import bg from './img/bg.png'
import sequenceflow from './img/tools/sequenceflow.png'
import task from './img/tools/task.png'
import split from './img/tools/split.svg'

import {
    bindDomEvent,
    browser,
    createDomElement,
    distanceToLine,
    eventStop,
    exportBlob,
    exportTextFile,
    pathDToPoints,
    uuid
} from "../util"

import {
    connectArrowPrefix,
    createColorMarker,
    getElementDatas,
    HtmlElementData,
    HtmlSplitElementData,
    HtmlTextElementData,
    setElementDatas,
    svgNS
} from "../ElementData"
import historyActions from "../modules/history"
import SvgPaper from "../SvgPaper";

const {isChrome} = browser;
const xmlns = `xmlns="${svgNS}"`;
const fitStyle = "width: 100%;height: 100%;";
const {sqrt, min, max, abs, sin, cos, tan, atan} = Math;
const {removeEventListener, addEventListener} = document;
const {assign, keys, values} = Object;
const appendSvgInner = (inner) => {
    return `<svg style="${fitStyle}vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" ${xmlns}>${inner}</svg>`;
}

// 内置html块
const DefaultHtmlTypes = {
    empty: ``,
    select: appendSvgInner(`<path d="M736.21333333 638.08l69.76-54.08c5.22666667-4.05333333 8-10.45333333 7.46666667-17.06666667-0.53333333-6.61333333-4.37333333-12.48-10.24-15.57333333L450.02666666 361.38666666c-6.82666667-3.62666667-15.04-2.98666667-21.12 1.70666667-6.08 4.69333333-8.85333333 12.58666667-7.04 20.05333333l95.57333334 389.54666667c1.6 6.4 6.29333333 11.62666667 12.48 13.76 6.18666667 2.13333333 13.12 1.06666667 18.34666666-2.98666667l69.86666667-54.08L769.06666666 924.37333333c3.2 4.05333333 7.78666667 6.72 12.90666667 7.36 0.85333333 0.10666667 1.6 0.10666667 2.45333333 0.10666667 4.26666667 0 8.53333333-1.38666667 11.94666667-4.05333334l87.25333333-67.52c8.53333333-6.61333333 10.02666667-18.88 3.52-27.30666666L736.21333333 638.08zM178.24 282.02666666l-31.25333334-21.01333333c-14.61333333 21.76-27.30666667 44.90666667-37.86666666 68.90666667l34.56 15.14666666c9.6-21.86666667 21.22666667-43.09333333 34.56-63.04zM549.54666666 103.89333333l3.2-37.54666667c-26.02666667-2.24-52.69333333-2.13333333-78.50666666 0.21333334l3.41333333 37.54666666c23.57333333-2.13333333 48-2.24 71.89333333-0.21333333zM120.74666666 413.22666666l-36.69333333-8.74666666c-6.08 25.38666667-9.92 51.52-11.30666667 77.76l37.65333334 2.13333333c1.38666667-24 4.8-47.89333333 10.34666666-71.14666667zM112.64 556.26666666l-37.44 4.48c3.09333333 26.02666667 8.64 51.94666667 16.32 76.90666667l36.05333333-11.09333333c-7.04-22.82666667-12.05333333-46.50666667-14.93333333-70.29333334zM919.36 327.46666666c-10.66666667-23.89333333-23.46666667-46.93333333-38.29333334-68.58666666l-31.14666666 21.22666666c13.44 19.84 25.28 40.96 34.98666666 62.82666667l34.45333334-15.46666667zM831.36 197.97333333c-18.34666667-18.77333333-38.4-35.94666667-59.62666667-51.09333333L749.86666666 177.6c19.52 13.86666667 37.86666667 29.65333333 54.61333334 46.82666666l26.88-26.45333333zM703.89333333 107.09333333c-23.68-11.2-48.53333333-20.37333333-73.81333333-27.09333333l-9.81333334 36.37333333c23.14666667 6.18666667 45.86666667 14.61333333 67.52 24.85333333l16.10666667-34.13333333zM277.76 178.98666666l-22.08-30.50666666c-21.22666667 15.36-41.17333333 32.64-59.41333334 51.52l27.09333334 26.13333333c16.74666667-17.28 35.09333333-33.06666667 54.4-47.14666667zM348.90666666 918.08c24.32 9.81333333 49.6 17.49333333 75.2 22.82666666l7.68-36.90666666c-23.46666667-4.90666667-46.61333333-11.94666667-68.8-20.90666667l-14.08 34.98666667zM503.25333333 912.42666666l-1.06666667 37.65333334c4.16 0.10666667 8.42666667 0.21333333 12.58666667 0.21333333 21.97333333 0 44.16-1.6 65.81333333-4.90666667l-5.54666666-37.22666666c-23.57333333 3.52-47.89333333 4.90666667-71.78666667 4.26666666zM944.85333333 401.81333333l-36.58666667 8.96c5.65333333 23.14666667 9.28 47.14666667 10.77333334 71.04l37.65333333-2.34666667c-1.70666667-26.13333333-5.65333333-52.26666667-11.84-77.65333333zM396.8 80.64c-25.28 6.93333333-50.02666667 16.21333333-73.6 27.62666666l16.32 33.92c21.54666667-10.34666667 44.26666667-18.88 67.30666666-25.17333333L396.8 80.64zM121.17333333 710.4c11.94666667 23.25333333 26.13333333 45.54666667 42.13333333 66.34666666l29.86666667-22.93333333c-14.61333333-18.98666667-27.52-39.46666667-38.50666667-60.69333333L121.17333333 710.4zM216.42666666 834.66666666c19.30666667 17.70666667 40.42666667 33.70666667 62.50666667 47.68l20.05333333-31.89333333c-20.26666667-12.8-39.46666667-27.41333333-57.17333333-43.62666667l-25.38666667 27.84z"></path>`),
    start: appendSvgInner(`<path d="M400.43759 732.273456a71.33239 71.33239 0 0 0 34.157473 8.810938 75.556813 75.556813 0 0 0 41.157944-12.069778l219.790665-144.837341a85.574729 85.574729 0 0 0 38.502593-72.418671 83.402169 83.402169 0 0 0-37.295615-70.608203l-221.842527-144.837341a72.41867 72.41867 0 0 0-74.591231-3.741631 84.488449 84.488449 0 0 0-41.761433 75.436115v289.674681a84.488449 84.488449 0 0 0 41.882131 74.591231z m42.002829-82.315889V374.163131l207.600188 135.664309v0.965582a13.156058 13.156058 0 0 1 0 2.293258z"></path><path d="M149.989688 874.093352a509.948138 509.948138 0 1 0-109.714286-162.700613 513.206978 513.206978 0 0 0 109.714286 162.700613zM84.571489 512a428.11504 428.11504 0 1 1 427.511551 428.11504A428.597831 428.597831 0 0 1 84.571489 512z" ></path>`),
    end: appendSvgInner(`<path d="M512 0c282.752 0 512 229.248 512 512s-229.248 512-512 512S0 794.752 0 512 229.248 0 512 0z m0 85.333333C276.352 85.333333 85.333333 276.352 85.333333 512s191.018667 426.666667 426.666667 426.666667 426.666667-191.018667 426.666667-426.666667S747.648 85.333333 512 85.333333z m85.333333 256a85.333333 85.333333 0 0 1 85.333334 85.333334v170.666666a85.333333 85.333333 0 0 1-85.333334 85.333334h-170.666666a85.333333 85.333333 0 0 1-85.333334-85.333334v-170.666666a85.333333 85.333333 0 0 1 85.333334-85.333334h170.666666z"></path>`),
    // 网关（默认xor）
    xor: appendSvgInner(`<path d="M485.871492 47.174338c-9.168729-0.008066-18.33651 3.392487-25.137616 10.224908L57.381424 460.755494c-13.603161 13.602212-13.540056 36.674443 0.062631 50.276655l403.288872 403.286025c13.603161 13.606956 36.673969 13.66674 50.276655 0l403.352926-403.350079c13.602686-13.601737 13.539581-36.674918-0.064054-50.27713L511.009107 57.399246c-6.801106-6.801106-15.969361-10.217316-25.137615-10.224908z m-0.030841 59.805036l378.943153 378.946-378.943153 378.94173-378.943153-378.94173 378.943153-378.946zM344.31891 317.829311c-0.006643 0-4.560641 0.872083-4.564436 0.872083-0.004745 0-3.864114 2.615773-3.866961 2.615773l-14.581525 14.584847c-0.004745 0-2.661797 3.902546-2.663696 3.94857 0 0.004745-0.824161 4.498011-0.823686 4.498011 0 0.004745 0.886317 4.452936 0.88774 4.452935 0 0.004745 2.532741 3.94857 2.535588 3.94857l133.188084 133.184763-133.12403 133.12498v-0.041754c0 0.004745-2.661797 3.947621-2.663696 3.947621 0 0.004745-0.823686 4.499434-0.823686 4.499434 0 0.009489 0.886317 4.452936 0.88774 4.452936 0 0 2.533215 3.901597 2.535588 3.947621l14.582474 14.579627c0.004745 0.004745 3.990798 2.617197 3.994119 2.617197 0.004745 0 4.434431 0.872083 4.438227 0.872083 0.004745 0 4.497536-0.825584 4.500858-0.825584 0.004745 0 3.928642-2.663695 3.931014-2.663696l133.125929-133.128775 133.154871 133.156769c0.004745 0.004745 3.991273 2.617197 3.99412 2.617197 0.004745 0 4.434431 0.872083 4.438226 0.872083 0.004745 0 4.498011-0.827008 4.501807-0.827008 0.004745 0 3.926744-2.661797 3.929116-2.661797l14.582949-14.580577c0.004745-0.004745 2.597743-3.855573 2.600116-3.855573 0-0.004745 0.88774-4.545458 0.88774-4.591007 0-0.004745-0.886791-4.452936-0.888689-4.452936 0 0-2.59632-3.994119-2.599167-3.994119l-133.140163-133.142535 133.141112-133.139214c0.004745 0 2.596794-3.856048 2.599167-3.856048 0-0.004745 0.88774-4.544509 0.88774-4.544509 0-0.009489-0.886317-4.452936-0.887266-4.452935 0-0.004745-2.659899-3.94857-2.662746-3.94857l-14.582949-14.584372c-0.004745 0-3.863639-2.616248-3.86696-2.616248-0.004745 0-4.433957-0.873032-4.437753-0.873032-0.004745 0-4.561116 0.873032-4.564436 0.873032-0.004745 0-3.864588 2.616248-3.866961 2.616248l-133.143484 133.143484-133.203268-133.207538v-0.041754c-0.004745 0-3.927693-2.5242-3.931014-2.5242-0.004745 0-4.431584-0.871608-4.436804-0.872083h-0.000949z"></path>`),
    or: appendSvgInner(`<path d="M485.871492 47.174338c-9.168729-0.008066-18.33651 3.392487-25.137616 10.224908L57.381424 460.755494c-13.603161 13.602212-13.540056 36.674443 0.062631 50.276655l403.288872 403.286025c13.603161 13.606956 36.673969 13.66674 50.276655 0l403.352926-403.350079c13.602686-13.601737 13.539581-36.674918-0.064054-50.27713L511.009107 57.399246c-6.801106-6.801106-15.969361-10.217316-25.137615-10.224908z m-0.030841 59.805036l378.943153 378.946-378.943153 378.94173-378.943153-378.94173 378.943153-378.946z m-8.957589 186.164969l-0.000949 0.197381c0 0.004745-3.969921 0.735434-3.971345 0.735435-0.004745 0-3.251568 2.203456-3.25394 2.203455-0.004745 0-2.260393 3.350733-2.262291 3.350734 0 0.004745-0.763903 3.994119-0.765326 3.994119V439.440713l-96.147347-96.147348h0.004745c-0.004745 0-3.414787-2.203456-3.416211-2.203456-0.005694 0-3.803856-0.771969-3.832798-0.777188-0.037009 0.005694-3.945249 0.732113-3.947147 0.732113-0.004745-0.004745-3.352157 2.249005-3.35358 2.249006l-12.636658 12.629066-2.306416 3.397231c-0.004745 0-0.713608 3.901597-0.713609 3.901597 0 0.004745 0.765801 3.856048 0.766275 3.856048 0 0.004745 2.188747 3.397232 2.189696 3.397232l96.099901 96.101323H303.419279l-0.026571-0.032264c0 0.004745-3.953315 0.871608-3.955212 0.871608-0.004745 0.004745-3.250619 2.20393-3.252992 2.20393v-0.023249c-0.004745 0.004745-2.259918 3.352157-2.261816 3.352157-0.004745 0-0.764852 3.993171-0.766275 3.99317v17.871526s0.759158 4.040143 0.786677 4.040143c0 0.009489 2.251378 3.259634 2.251852 3.259635 0.004745 0 3.271022 2.203456 3.271971 2.203455 0.004745 0.009489 3.970396 0.872083 3.971819 0.872083h135.870285L343.2912 601.205873v-0.03606c-0.004745 0.004745-2.305942 3.397232-2.306891 3.397231-0.004745 0.004745-0.714557 3.901597-0.714557 3.901597 0 0.009015 0.767224 3.856048 0.767698 3.856048 0 0 2.196813 3.397232 2.189696 3.397232l12.638556 12.629066c0.004745 0.004745 3.453219 2.249005 3.454168 2.249005l3.847033 0.780509c0.005694 0 3.896852-0.734011 3.89875-0.734011 0 0 3.402451-2.295504 3.403874-2.295503l96.160633-96.160633v136.107996l-0.025147-0.026571c0 0.004745 0.787626 4.039194 0.786677 4.039195 0 0.004745 2.251378 3.259634 2.251378 3.259634 0.004745 0.004745 3.271022 2.203456 3.271971 2.203456 0 0 3.942402 0.826059 3.971819 0.872082l17.865832-0.004745c0.004745 0 4.048209-0.825584 4.050582-0.825584 0 0 3.250619-2.20393 3.252991-2.20393 0.004745-0.004745 2.24948-3.25916 2.251853-3.25916 0 0 0.774816-4.040143 0.776713-4.040143v-136.030657l96.091834 96.087564c-0.004745 0.009489 3.452271 2.249005 3.454168 2.249005 0.004745 0.004745 3.846084 0.735434 3.847508 0.735435 0.005694 0 3.896378-0.735434 3.89875-0.735435 0 0 3.401976-2.295504 3.404823-2.295503l12.632862-12.637132c0.004745 0 2.239041-3.352157 2.26751-3.352157 0-0.009489 0.778137-3.947621 0.759157-3.99317 0-0.004745-0.767698-3.856997-0.768647-3.856997-0.005694 0.004745-2.236194-3.442781-2.265612-3.442781l-95.851276-95.857444h135.679072c0.004745 0.004745 4.048209-0.826533 4.049633-0.826533 0.004745 0 3.251568-2.20393 3.252991-2.203931 0.004745-0.009015 2.249005-3.25916 2.251852-3.259159 0 0 0.774816-4.039194 0.776714-4.039195v-17.867255c0-0.004745-0.771494-3.947621-0.770071-3.99412-0.004745-0.004745-2.240465-3.350733-2.267984-3.350733-0.004745-0.004745-3.271496-2.20393-3.27292-2.20393 0 0.004745-4.036822-0.871608-4.03872-0.825584h-136.09044l96.283047-96.280201c0.004745-0.004745 2.238092-3.351682 2.266561-3.351682 0-0.009489 0.77956-3.947621 0.77956-3.947621 0-0.004745-0.766749-3.856522-0.767224-3.856522-0.004745-0.004745-2.299299-3.397232-2.300248-3.397231l-12.635709-12.638082c-0.004745 0-3.341244-2.249005-3.342667-2.249005-0.004745-0.004745-3.846084-0.734485-3.848456-0.734485-0.005694 0-3.960432 0.734011-3.961855 0.734011-0.004745 0.004745-3.352631 2.249005-3.35358 2.249005l-96.162531 96.161582V303.47838c0-0.004745-0.771494-3.948096-0.770071-3.994119-0.004745-0.009489-2.239516-3.351208-2.239516-3.351208-0.004745-0.004745-3.271496-2.203456-3.272445-2.203456-0.004745 0-4.048209-0.780509-4.049158-0.780509l-17.868679-0.004745z m-113.645937 47.167429l0.016132-0.002372h-0.029892l0.014235 0.002847z"></path>`),
    and: appendSvgInner(`<path d="M485.871492 47.174338c-9.168729-0.008066-18.33651 3.392487-25.137616 10.224908L57.381424 460.755494c-13.603161 13.602212-13.540056 36.674443 0.062631 50.276655l403.288872 403.286025c13.603161 13.606956 36.673969 13.66674 50.276655 0l403.352926-403.350079c13.602686-13.601737 13.539581-36.674918-0.064054-50.27713L511.009107 57.399246c-6.801106-6.801106-15.969361-10.217316-25.137615-10.224908z m-0.030841 59.805036l378.943153 378.946-378.943153 378.94173-378.943153-378.94173 378.943153-378.946z m-10.34495 156.490911c0 0.004745-4.581992 0.872083-4.584365 0.872082-0.004745 0-3.752138 2.525623-3.754984 2.525624-0.004745 0.004745-2.608182 3.856048-2.610555 3.856047 0 0.004745-0.882521 4.590533-0.883944 4.590533V463.610402H275.283938l-0.03179-0.037009c0 0.004745-4.563488 1.010154-4.56586 1.010155-0.004745 0.004745-3.751663 2.525149-3.754036 2.525149l0.003796-0.046499c-0.004745 0.004745-2.608182 3.856048-2.610554 3.856048-0.004745 0-0.882996 4.591007-0.884893 4.591007v20.625371s0.876827 4.641776 0.908142 4.673566c0 0.004745 2.599167 3.764474 2.599167 3.764474 0.004745 0.004745 3.775387 2.525149 3.776811 2.525149 0.004745 0.004745 4.581518 1.009206 4.583415 1.009206h188.355615v188.264041l-0.029892-0.02752c0 0.004745 0.909092 4.673566 0.908617 4.673566 0 0.004745 2.598218 3.764 2.598218 3.764 0.004745 0.004745 3.776811 2.5242 3.777285 2.5242 0 0 4.550203 0.964131 4.584365 1.010629l20.6211-0.004745c0.005219 0 4.671668-0.964131 4.674515-0.964131 0 0 3.752138-2.5242 3.754985-2.5242 0.004745-0.004745 2.595371-3.765423 2.598218-3.765423 0 0 0.895332-4.659332 0.896755-4.659331V508.125523h188.309116c0.005219 0.004745 4.672142-0.963656 4.67404-0.963656 0.004745 0 3.753087-2.525149 3.754985-2.525149 0.004745-0.004745 2.595846-3.764474 2.598692-3.764474 0 0 0.894857-4.659332 0.896756-4.659332v-20.6211c0-0.004745-0.890587-4.545458-0.889164-4.591008-0.004745-0.004745-2.585882-3.856048-2.618146-3.856047-0.004745-0.004745-3.775862-2.5242-3.77681-2.5242 0 0.004745-4.659332-1.010629-4.661704-0.964605h-188.289188V275.364866c0-0.004745-0.891062-4.544034-0.889638-4.590059-0.004745-0.004745-2.585882-3.856997-2.585882-3.856996-0.004745-0.004745-3.774913-2.5242-3.775862-2.5242-0.004745 0-4.672617-0.918581-4.674514-0.918582l-20.624897-0.004744z"></path>`),
    // 聚合网关
    join: `<svg style="${fitStyle}transform: rotate(45deg); vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" ${xmlns}><path d="M485.871492 47.174338c-9.168729-0.008066-18.33651 3.392487-25.137616 10.224908L57.381424 460.755494c-13.603161 13.602212-13.540056 36.674443 0.062631 50.276655l403.288872 403.286025c13.603161 13.606956 36.673969 13.66674 50.276655 0l403.352926-403.350079c13.602686-13.601737 13.539581-36.674918-0.064054-50.27713L511.009107 57.399246c-6.801106-6.801106-15.969361-10.217316-25.137615-10.224908z m-0.030841 59.805036l378.943153 378.946-378.943153 378.94173-378.943153-378.94173 378.943153-378.946zM344.31891 317.829311c-0.006643 0-4.560641 0.872083-4.564436 0.872083-0.004745 0-3.864114 2.615773-3.866961 2.615773l-14.581525 14.584847c-0.004745 0-2.661797 3.902546-2.663696 3.94857 0 0.004745-0.824161 4.498011-0.823686 4.498011 0 0.004745 0.886317 4.452936 0.88774 4.452935 0 0.004745 2.532741 3.94857 2.535588 3.94857l133.188084 133.184763-133.12403 133.12498v-0.041754c0 0.004745-2.661797 3.947621-2.663696 3.947621 0 0.004745-0.823686 4.499434-0.823686 4.499434 0 0.009489 0.886317 4.452936 0.88774 4.452936 0 0 2.533215 3.901597 2.535588 3.947621l14.582474 14.579627c0.004745 0.004745 3.990798 2.617197 3.994119 2.617197 0.004745 0 4.434431 0.872083 4.438227 0.872083 0.004745 0 4.497536-0.825584 4.500858-0.825584 0.004745 0 3.928642-2.663695 3.931014-2.663696l133.125929-133.128775 133.154871 133.156769c0.004745 0.004745 3.991273 2.617197 3.99412 2.617197 0.004745 0 4.434431 0.872083 4.438226 0.872083 0.004745 0 4.498011-0.827008 4.501807-0.827008 0.004745 0 3.926744-2.661797 3.929116-2.661797l14.582949-14.580577c0.004745-0.004745 2.597743-3.855573 2.600116-3.855573 0-0.004745 0.88774-4.545458 0.88774-4.591007 0-0.004745-0.886791-4.452936-0.888689-4.452936 0 0-2.59632-3.994119-2.599167-3.994119l-133.140163-133.142535 133.141112-133.139214c0.004745 0 2.596794-3.856048 2.599167-3.856048 0-0.004745 0.88774-4.544509 0.88774-4.544509 0-0.009489-0.886317-4.452936-0.887266-4.452935 0-0.004745-2.659899-3.94857-2.662746-3.94857l-14.582949-14.584372c-0.004745 0-3.863639-2.616248-3.86696-2.616248-0.004745 0-4.433957-0.873032-4.437753-0.873032-0.004745 0-4.561116 0.873032-4.564436 0.873032-0.004745 0-3.864588 2.616248-3.866961 2.616248l-133.143484 133.143484-133.203268-133.207538v-0.041754c-0.004745 0-3.927693-2.5242-3.931014-2.5242-0.004745 0-4.431584-0.871608-4.436804-0.872083h-0.000949z"></path></svg>`,
    manual: appendSvgInner(`<path d="M512 0C230.4 0 0 230.4 0 512s230.4 512 512 512 512-230.4 512-512S793.6 0 512 0zM512 960c-249.6 0-448-198.4-448-448 0-249.6 198.4-448 448-448 249.6 0 448 198.4 448 448C960 761.6 761.6 960 512 960z"></path><path d="M704 320c0-108.8-83.2-192-192-192C403.2 128 320 211.2 320 320s83.2 192 192 192C620.8 512 704 428.8 704 320zM512 448C441.6 448 384 390.4 384 320c0-70.4 57.6-128 128-128 70.4 0 128 57.6 128 128C640 390.4 582.4 448 512 448z"></path><path d="M512 512c-179.2 0-320 115.2-320 256 0 19.2 12.8 32 32 32S256 787.2 256 768c0-108.8 115.2-192 256-192 140.8 0 256 83.2 256 192 0 19.2 12.8 32 32 32S832 787.2 832 768C832 627.2 691.2 512 512 512z"></path>`),
    business: appendSvgInner(`<path d="M91.18976 196.096v631.808h841.62048V196.096H91.18976z m35.84 214.016H289.28v176.351232H127.02976V410.112zM325.12 410.112h571.85024v176.351232H325.12V410.112z m-198.09024 212.191232H289.28V792.064H127.02976v-169.760768z m198.09024 0h571.85024V792.064H325.12v-169.760768z"></path>`),
    businessTask: appendSvgInner(`<path d="M219.9808 107.52C110.046208 107.52 20.48 196.708352 20.48 306.432v411.136c0 109.723648 89.566208 198.912 199.5008 198.912h584.0384c109.934592 0 199.5008-89.188352 199.5008-198.912v-411.136c0-109.723648-89.566208-198.912-199.5008-198.912H219.9808z m0 61.44h584.0384C881.094144 168.96 942.08 229.798912 942.08 306.432v411.136c0 76.633088-60.985856 137.472-138.0608 137.472H219.9808C142.905856 855.04 81.92 794.201088 81.92 717.568v-411.136C81.92 229.798912 142.905856 168.96 219.9808 168.96z m-36.82304 73.680896v389.05856h518.257152v-389.05856H183.158272z m22.070272 134.710272h474.118144v105.67168H327.20896v-105.41056h-22.06976v105.41056h-99.91168v-105.67168z m0 127.741952h99.911168v104.536064h-99.91168V505.09312z m121.980928 0h352.137216v104.536064H327.20896V505.09312z"></path><path d="M207.238144 267.552256v110.1696h471.880704V267.552256z"></path>`),
    service: appendSvgInner(`<path d="M688 80v44.992a180.672 180.672 0 0 0-72.992 30.016l-33.024-31.008-44 47.008 32 29.984a180 180 0 0 0-28.992 71.008H496v64h44.992c4.672 26.528 15.168 50.752 30.016 72l-34.016 32.992 46.016 46.016 32.992-34.016a179.776 179.776 0 0 0 72 30.016V528h64v-44.992a180 180 0 0 0 71.008-28.992l29.984 32 47.008-44.032-31.008-32.96c15.072-21.44 25.28-46.24 30.016-73.024H944v-64h-44.992a179.776 179.776 0 0 0-30.016-72l30.016-30.016-45.024-44.992-29.984 30.016a179.776 179.776 0 0 0-72-30.016V80h-64z m32 106.016A117.248 117.248 0 0 1 838.016 304a117.248 117.248 0 0 1-118.016 118.016A117.248 117.248 0 0 1 601.984 304 117.248 117.248 0 0 1 720 185.984zM304.992 374.976l-59.008 24 23.04 57.984a221.12 221.12 0 0 0-75.04 74.016l-56.96-23.008-24 59.008 56.96 22.976a216.832 216.832 0 0 0-6.976 53.024c0 18.24 2.72 36.032 6.976 52.992l-56.96 23.008 24 58.976 56.96-22.976a219.872 219.872 0 0 0 75.008 74.976l-23.008 57.024 59.008 24 23.008-57.024a215.36 215.36 0 0 0 52.992 7.04c18.24 0 36.096-2.752 52.992-7.04l23.04 57.024 58.976-24-23.008-57.024a221.12 221.12 0 0 0 74.016-74.976l57.984 22.976 24-58.976-57.984-23.008c4.224-16.96 6.976-34.784 6.976-52.992 0-18.24-2.752-36.096-6.976-53.024l57.984-22.976-24-59.008-57.984 23.008a220 220 0 0 0-74.016-74.016l23.008-57.984-59.008-24-23.008 57.984a216.896 216.896 0 0 0-52.992-7.008c-18.24 0-36.032 2.784-52.992 7.04l-23.008-58.016z m76 115.008a152.096 152.096 0 0 1 152.992 152.96c0 85.248-67.776 154.016-152.96 154.016a153.792 153.792 0 0 1-154.016-153.984c0-85.216 68.8-153.024 153.984-153.024z"></path>`),
    message: appendSvgInner(`<path d="M580.266667 733.866667c0 18.773333-30.72 34.133333-68.266667 34.133333s-68.266667-15.36-68.266667-34.133333h136.533334zM717.141333 716.8H306.858667c-14.336 0-26.624-8.874667-31.744-22.186667-5.12-13.653333-1.024-28.672 9.898666-37.888l52.906667-45.056c3.754667-3.413333 6.144-8.192 6.144-13.312V443.733333c0-94.208 75.434667-170.666667 167.936-170.666666s167.936 76.458667 167.936 170.666666v154.624c0 5.12 2.048 9.898667 6.144 13.312l52.906667 45.056c10.922667 9.216 15.018667 24.234667 9.898666 37.888-5.12 13.312-17.408 22.186667-31.744 22.186667zM512 307.2c-73.728 0-133.802667 61.098667-133.802667 136.533333v154.624c0 15.36-6.485333 29.354667-18.090666 39.253334L307.2 682.666667h409.941333v17.066666-17.066666l-53.248-45.056c-11.605333-9.557333-18.090667-23.893333-18.090666-39.253334V443.733333c0-75.434667-60.074667-136.533333-133.802667-136.533333z"></path><path d="M512 597.333333a16.657067 16.657067 0 0 1-15.018667-24.576l21.845334-43.690666H477.866667a17.749333 17.749333 0 0 1-14.677334-8.192 17.169067 17.169067 0 0 1-0.682666-16.725334l34.133333-68.266666c4.096-8.533333 14.336-11.946667 22.869333-7.509334 8.533333 4.096 11.946667 14.336 7.509334 22.869334l-21.845334 43.690666H546.133333c5.802667 0 11.264 3.072 14.677334 8.192 3.072 5.12 3.413333 11.264 0.682666 16.725334l-34.133333 68.266666c-3.072 5.802667-9.216 9.216-15.36 9.216z" ></path><path d="M512 1024C229.717333 1024 0 794.282667 0 512S229.717333 0 512 0s512 229.717333 512 512-229.717333 512-512 512z m0-975.189333C256.682667 48.810667 48.810667 256.682667 48.810667 512S256.682667 975.189333 512 975.189333 975.189333 767.317333 975.189333 512 767.317333 48.810667 512 48.810667z"></path><path d="M541.354667 301.738667c2.730667-4.778667 4.778667-10.24 4.778666-16.384 0-18.773333-15.36-34.133333-34.133333-34.133334s-34.133333 15.36-34.133333 34.133334c0 6.144 2.048 11.605333 4.778666 16.384h58.709334z"></path>`),
    reset: appendSvgInner(`<path d="M790.2 590.67l105.978 32.29C847.364 783.876 697.86 901 521 901c-216.496 0-392-175.504-392-392s175.504-392 392-392c108.502 0 206.708 44.083 277.685 115.315l-76.64 76.64C670.99 257.13 599.997 225 521.5 225 366.032 225 240 351.032 240 506.5 240 661.968 366.032 788 521.5 788c126.148 0 232.916-82.978 268.7-197.33z"></path><path d="M855.58 173.003L650.426 363.491l228.569 32.285z"></path>`),
    imp: appendSvgInner(`<path d="M746.666667 469.333333H554.666667V213.333333l42.666666 42.666667 51.2 51.2L746.666667 213.333333l59.733333 59.733334-98.133333 98.133333 21.333333 21.333333 38.4 34.133334 42.666667 42.666666h-64zM512 213.333333v85.333334H298.666667v426.666666h426.666666v-213.333333h85.333334v298.666667H213.333333V213.333333h298.666667z"></path>`),
    exp: appendSvgInner(`<path d="M469.333333 162.133333h85.333334v469.333334c0 12.8-8.533333 21.333333-21.333334 21.333333h-42.666666c-12.8 0-21.333333-8.533333-21.333334-21.333333v-469.333334z"></path><path d="M315.733333 392.533333L285.866667 362.666667c-8.533333-8.533333-8.533333-21.333333 0-29.866667l211.2-211.2c8.533333-8.533333 21.333333-8.533333 29.866666 0l44.8 44.8-226.133333 226.133333c-8.533333 8.533333-21.333333 8.533333-29.866667 0z"></path><path d="M452.266667 166.4l44.8-44.8c8.533333-8.533333 21.333333-8.533333 29.866666 0l211.2 211.2c8.533333 8.533333 8.533333 21.333333 0 29.866667l-29.866666 29.866666c-8.533333 8.533333-21.333333 8.533333-29.866667 0L452.266667 166.4zM896 503.466667h-42.666667c-12.8 0-21.333333 8.533333-21.333333 21.333333v277.333333c0 12.8-8.533333 21.333333-21.333333 21.333334H213.333333c-12.8 0-21.333333-8.533333-21.333333-21.333334v-277.333333c0-12.8-8.533333-21.333333-21.333333-21.333333H128c-12.8 0-21.333333 8.533333-21.333333 21.333333v362.666667c0 12.8 8.533333 21.333333 21.333333 21.333333h768c12.8 0 21.333333-8.533333 21.333333-21.333333v-362.666667c0-12.8-8.533333-21.333333-21.333333-21.333333z"></path><path d="M277.333333 588.8H149.333333v-85.333333h128c12.8 0 21.333333 8.533333 21.333334 21.333333v42.666667c0 10.666667-8.533333 21.333333-21.333334 21.333333zM874.666667 588.8h-128c-12.8 0-21.333333-8.533333-21.333334-21.333333v-42.666667c0-12.8 8.533333-21.333333 21.333334-21.333333h128v85.333333z"></path>`),
    picture: appendSvgInner(`<path d="M986.112 446.7712a38.4 38.4 0 0 0 38.4-38.4V144.128a140.9536 140.9536 0 0 0-140.8-140.8L140.7488 3.6864a140.9536 140.9536 0 0 0-140.8 140.8v735.3856a140.9536 140.9536 0 0 0 140.8 140.8l742.9632-0.4096a140.9536 140.9536 0 0 0 140.8-140.8V588.288c0-3.6864-1.1264-7.0144-2.0992-10.3936a37.8368 37.8368 0 0 0-11.9808-29.5936L785.8176 342.6304c-26.0096-23.8592-65.8432-24.576-96.2048 1.9968l-163.1232 182.8864-146.2272-84.9408a70.8608 70.8608 0 0 0-53.3504-13.6192 70.3488 70.3488 0 0 0-44.544 26.4704L179.6096 563.8656a38.4 38.4 0 0 0 55.7056 52.8384l103.8336-109.568 145.9712 84.8384c25.9584 20.0192 62.976 18.9952 91.5968-5.888l162.3552-182.1184 208.5888 191.0272v284.4672c0 35.2768-28.7232 64-64 64l-742.912 0.4096c-35.2768 0-64-28.7232-64-64V144.4864c0-35.2768 28.7232-64 64-64l742.9632-0.4096c35.2768 0 64 28.7232 64 64v264.2944c0 21.1968 17.2032 38.4 38.4 38.4z"></path><path d="M264.4992 248.4224m-49.664 0a49.664 49.664 0 1 0 99.328 0 49.664 49.664 0 1 0-99.328 0Z"></path>`),
    del: `<svg style="${fitStyle}vertical-align: middle;fill: currentColor;overflow: hidden;transform: scale(1.2);" viewBox="0 0 1024 1024" version="1.1" ${xmlns}><path d="M202.666667 256h-42.666667a32 32 0 0 1 0-64h704a32 32 0 0 1 0 64H266.666667v565.333333a53.333333 53.333333 0 0 0 53.333333 53.333334h384a53.333333 53.333333 0 0 0 53.333333-53.333334V352a32 32 0 0 1 64 0v469.333333c0 64.8-52.533333 117.333333-117.333333 117.333334H320c-64.8 0-117.333333-52.533333-117.333333-117.333334V256z m224-106.666667a32 32 0 0 1 0-64h170.666666a32 32 0 0 1 0 64H426.666667z m-32 288a32 32 0 0 1 64 0v256a32 32 0 0 1-64 0V437.333333z m170.666666 0a32 32 0 0 1 64 0v256a32 32 0 0 1-64 0V437.333333z"></path></svg>`,
    overview: `<svg style="${fitStyle}vertical-align: middle;fill: currentColor;overflow: hidden;transform: scale(0.85);" viewBox="0 0 1024 1024" version="1.1" ${xmlns}><path d="M512 349.888A162.112 162.112 0 1 0 674.112 512 162.32 162.32 0 0 0 512 349.888z m0 231.6A69.488 69.488 0 1 1 581.488 512 69.552 69.552 0 0 1 512 581.488z"></path><path d="M972.8 460.8h-54.4a410.304 410.304 0 0 0-355.2-355.2V51.2a51.2 51.2 0 1 0-102.4 0v54.4a410.304 410.304 0 0 0-355.2 355.2H51.2a51.2 51.2 0 1 0 0 102.4h54.4a410.304 410.304 0 0 0 355.2 355.2v54.4a51.2 51.2 0 0 0 102.4 0v-54.4a410.304 410.304 0 0 0 355.2-355.2h54.4a51.2 51.2 0 0 0 0-102.4zM512 819.2A307.2 307.2 0 1 1 819.2 512 307.456 307.456 0 0 1 512 819.2z"></path></svg>`,
    zoomReset: appendSvgInner(`<path d="M514.2 854.1c-188 0-340.9-152.9-340.9-340.9s152.9-340.9 340.9-340.9 340.9 152.9 340.9 340.9-152.9 340.9-340.9 340.9z m0-598.8C372 255.3 256.3 371 256.3 513.2S372 771.1 514.2 771.1s257.9-115.7 257.9-257.9-115.7-257.9-257.9-257.9z"></path><path d="M888.5 554.7H742.1c-22.9 0-41.5-18.6-41.5-41.5s18.6-41.5 41.5-41.5h146.5c22.9 0 41.5 18.6 41.5 41.5-0.1 22.9-18.6 41.5-41.6 41.5zM288 554.7H141.5c-22.9 0-41.5-18.6-41.5-41.5s18.6-41.5 41.5-41.5H288c22.9 0 41.5 18.6 41.5 41.5s-18.6 41.5-41.5 41.5zM515 327.7c-22.9 0-41.5-18.6-41.5-41.5V139.7c0-22.9 18.6-41.5 41.5-41.5s41.5 18.6 41.5 41.5v146.4c0 23-18.5 41.6-41.5 41.6zM515 928.2c-22.9 0-41.5-18.6-41.5-41.5V740.3c0-22.9 18.6-41.5 41.5-41.5s41.5 18.6 41.5 41.5v146.5c0 22.8-18.5 41.4-41.5 41.4z"></path>`),
    zoomIn: appendSvgInner(`<path d="M919.264 905.984l-138.912-138.912C851.808 692.32 896 591.328 896 480c0-229.376-186.624-416-416-416S64 250.624 64 480s186.624 416 416 416c95.008 0 182.432-32.384 252.544-86.208l141.44 141.44a31.904 31.904 0 0 0 45.248 0 32 32 0 0 0 0.032-45.248zM128 480C128 285.92 285.92 128 480 128s352 157.92 352 352-157.92 352-352 352S128 674.08 128 480z"></path><path d="M625.792 448H512v-112a32 32 0 0 0-64 0V448h-112a32 32 0 0 0 0 64H448v112a32 32 0 1 0 64 0V512h113.792a32 32 0 1 0 0-64z"></path>`),
    zoomOut: appendSvgInner(`<path d="M919.264 905.984l-138.912-138.912C851.808 692.32 896 591.328 896 480c0-229.376-186.624-416-416-416S64 250.624 64 480s186.624 416 416 416c95.008 0 182.432-32.384 252.544-86.208l141.44 141.44a31.904 31.904 0 0 0 45.248 0 32 32 0 0 0 0.032-45.248zM128 480C128 285.92 285.92 128 480 128s352 157.92 352 352-157.92 352-352 352S128 674.08 128 480z" ></path><path d="M625.792 448H336a32 32 0 0 0 0 64h289.792a32 32 0 1 0 0-64z"></path>`),

    // 修改类型
    exchange:`<svg style="${fitStyle}vertical-align: middle;fill: currentColor;overflow: hidden;transform: scale(1.2);" viewBox="0 0 1024 1024" version="1.1" ${xmlns}><path d="M825.6 448a37.12 37.12 0 0 0-37.12 37.76v243.2a56.96 56.96 0 0 1-56.32 56.32H291.84a56.96 56.96 0 0 1-56.32-56.32v-441.6a56.96 56.96 0 0 1 56.32-56.32h248.32A37.12 37.12 0 0 0 576 192a33.28 33.28 0 0 0-37.12-32.64H291.84a128 128 0 0 0-128 128v439.68a128 128 0 0 0 128 128h439.68a128 128 0 0 0 128-128V488.32c-1.28-23.04-15.36-40.32-33.92-40.32z"></path><path d="M362.24 647.68a33.92 33.92 0 0 0 46.72 0l412.16-412.16a36.48 36.48 0 1 0-51.2-51.2L362.24 600.96a42.24 42.24 0 0 0 0 46.72z"></path></svg>`,
};

const defs = `<path d="M5,0 0,2.5 5,5 3.5,3 3.5,2z" id="${connectArrowPrefix}path"></path>`

/** 全局映射html块 */
const GlobalHTMLTypes = {...DefaultHtmlTypes};

/**
 * 注册html映射类型
 *
 * @param type
 * @param innerHTML
 */
export const registerHTML = (type, innerHTML) => {
    GlobalHTMLTypes[type] = innerHTML;
}

/**
 * 获取类型的html代码
 *
 * @param type
 * @param innerHTML
 */
export const getHTML = (type) => {
    return GlobalHTMLTypes[type];
}

/**
 * 拓展html节点的方法
 * @param type
 */
HtmlElementData.prototype.setHtmlType = function (type) {
    let html = GlobalHTMLTypes[type];
    if (html) {
        this.updateHTML(html);
    } else {
        console.error(`html type ['${type}'] is not register`);
    }
}

/**
 * popup menu element
 */
export class PopupMenuHtmlElementData extends HtmlElementData {
    #_data;
    #_option;

    constructor(node, option) {
        super(node);
        // init style
        Object.assign(node.style, {
            position: "absolute",
            borderRadius: "4px",
            padding: "5px",
            fontSize: "12px",
            background: "#fff",
            boxShadow: "0 1px 4px rgba(0,0,0,.3)"
        });
        this.#_option = option;
    }

    setData(data) {
        if (Array.isArray(data)) {
            this.#_data = data;
            this.#updateViews();
        }
        return this;
    }

    #updateViews() {
        let data = this.#_data;
        let html = [];
        let actions = {};
        let index = 0;
        for (let item of data) {
            let {type, text, action} = item;
            let typeSvg = DefaultHtmlTypes[type.toLowerCase()];
            actions[++index] = action;
            html.push(`<div data-index="${index}" style="display: flex; padding:4px; cursor: pointer;align-items: center;">
                        <div style="width: 18px;height: 18px">
                            ${typeSvg}
                        </div>
                        <div style="margin-left: 10px;">${text}</div>
                       </div>`);
        }
        this.updateHTML(html.join(""));
        let children = this.node.children;
        for (let child of children) {
            let index = child.dataset.index;
            bindDomEvent(child, "click", (evt) => {
                actions[index]();
            });
            bindDomEvent(child, "mouseover", () => {
                child.style.background = "#ddd";
            });
            bindDomEvent(child, "mouseout", () => {
                child.style.background = "unset";
            });
        }
        // stop event
        this.click((evt) => {
            eventStop(evt);
        }).dblclick((evt) => {
            eventStop(evt);
        })
    }
}


const NodeTypes = {
    Start: "Start",
    End: "End",
    Join: "Join",
    Split: "Split",
    Business: "Business",
    Service: "Service",
    Manual: "Manual",
    Message: "Message"
}

// options
import defaultOption, {DefaultSettings} from "./option/"

const mr = (type, draggable, title) => {
    let drag = draggable ? `draggable="true"` : "";
    return `<div class="menu-item" data-type="${type}" ${drag} title="${title}"></div>`
}
const tr = (type, title) => {
    return `<div class="tool-item" data-type="${type}" title="${title}"></div>`
}
const divider = `<div style="border: 1px dashed #dcdfe6; width: 40%;margin: 8px;opacity: .7;"></div>`;

/**
 * 菜单html模板
 *
 * @type {string}
 */
const extensionTemplate = `
    <div class="flow-menu" style="display:none;z-index: 100;">
        ${mr('select', 1, '圈选，可使用快捷键按住ctrl替代')}
        ${divider}
        ${mr('start', 1, '开始')}
        ${mr('end', 1, '结束')}
        ${divider}
        ${mr('businessTask', 1, '业务节点')}
        ${mr('service', 1, '服务节点')}
        ${mr('message', 1, '消息节点')}
        ${mr('manual', 1, '手工节点')}
        ${divider}
        ${mr('xor', 1, '有且仅有一个满足条件的分支通过')}
        ${mr('or', 1, '至少一个满足条件的分支通过,与汇聚网关组合使用')}
        ${mr('and', 1, '所有分支强制通过,与汇聚网关组合使用')}
        ${mr('join', 1, '汇聚网关')}
        ${divider}
        ${mr('reset', 0, '重置')}
        ${mr('imp', 0, '导入')}
        ${mr('exp', 0, '导出')}
    </div>
    <div class="flow-wrapper" style="position: absolute;width: 100%; height: 100%;left: 0; top: 0">
       <div class="flow-wrapper-body"  style="position: relative;${fitStyle}">
            <div class="text-editor" contenteditable style="min-width: 50px; height: 24px; display: none;position: absolute;font-size: 13px;background: #fff;transform: translate(-50%, -50%);outline: 1px solid transparent;z-index: 100;"></div>
       </div>
    </div>
    <div class="flow-tools" style="display:none;z-index: 100;">
        ${tr('overview', '大纲，全貌视图') + tr('zoomReset', '初始大小') + tr('zoomIn', '放大') + tr('zoomOut', '缩小')}
    </div>
    <input class="flow-import-file" type="file" accept=".json" style="display:none;opacity: 0;width: 0;"/>
`

// 连接创建时触发钩子
const onConnectCreated = (connect, instance) => {
    if (typeof instance.option.onConnectCreated == "function") {
        instance.option.onConnectCreated.call(instance, connect, instance);
    }
}

// 节点创建时触发钩子
const onNodeCreated = (node, instance) => {
    if (typeof instance.option.onNodeCreated == "function") {
        instance.option.onNodeCreated.call(instance, node, instance);
    }
}

/**
 * 定义流程设计类
 *
 * @author wangyunchao
 */
class FlowDesign {

    /**
     * new design
     *
     * @param dom
     * @param option 配置项
     */
    constructor(dom, option) {
        if (typeof dom == "string") {
            dom = document.querySelector(dom);
        }
        this.option = assign({}, defaultOption, option || {});
        if (!this.settings) {
            this.option.settings = {...DefaultSettings};
        } else {
            this.option.settings = assign({}, DefaultSettings, this.settings);
        }
        let {width = '100%', height = '100%'} = this.option;
        let rootStyle = {
            position: "relative",
            overflow: this.option.overflow || "hidden"
        };
        assign(dom.style, rootStyle);
        dom.innerHTML = extensionTemplate;
        if (this.option.menu) {
            this.initMenu(dom.children[0]);
        }
        this.initInput(dom.querySelector(".text-editor"));
        this.initFileInput(dom.querySelector(".flow-import-file"));

        let flowWrapper = dom.querySelector(".flow-wrapper-body");
        this.flowWrapper = flowWrapper;
        this.paper = new SvgPaper(flowWrapper, width, height);
        // connect colors
        this.connectColors = [this.themeColor];
        this.initPaper();
        this.dom = dom;
        this.init();
        this.initStyles();
        this.elements = {};
        this.groupSelectElements = [];
        this.selectElement = null;
        let me = this;
        let setUUID = (element) => {
            return me.setUUID(element);
        }
        this.nodeDatas = {
            gateway: "XOR",
            handler: {},
            uuid: setUUID,
            meta: {}
        };
        this.connectDatas = {
            priority: 0,
            conditionType: "Script",
            script: "",
            uuid: setUUID,
            meta: {},
            pathStyle: "broken"
        };

        this.initControlElements();
        this.cnt = 0;
        this.translateX = 0;
        this.translateY = 0;
        this.offsetX = 0;
        this.offsetY = 0;
        this.scaleValue = 1;
    };

    // 初始化及事件处理
    initControlElements() {
        let paper = this.paper;
        let me = this;
        let resizeOnMove = function (dx, dy, x, y) {
            me.resizeOnMove(this, dx, dy, x, y);
        };
        let resizeOnStart = function () {
            me.resizeOnStart(this);
            me.dragingElement = this;
        };
        let resizeOnUp = function () {
            me.dragingElement = null;
        };

        let attr = {
            fill: "#fff",
            stroke: this.themeColor
        }
        let args = [0, 0, 5, 5, 2.5];
        // 控制点信息
        let nw = this.nw = paper.rect(...args).attr({
            ...attr,
            cursor: 'nw-resize'
        });
        let w = this.w = paper.rect(...args).attr({
            ...attr,
            cursor: 'w-resize'
        });
        let sw = this.sw = paper.rect(...args).attr({
            ...attr,
            cursor: 'sw-resize'
        });
        let n = this.n = paper.rect(...args).attr({
            ...attr,
            cursor: 'n-resize'
        });
        let s = this.s = paper.rect(...args).attr({
            ...attr,
            cursor: 's-resize'
        });
        let ne = this.ne = paper.rect(...args).attr({
            ...attr,
            cursor: 'ne-resize'
        });
        let e = this.e = paper.rect(...args).attr({
            ...attr,
            cursor: 'e-resize'
        });
        let se = this.se = paper.rect(...args).attr({
            ...attr,
            cursor: 'se-resize'
        });
        let resizeFns = [resizeOnMove, resizeOnStart, resizeOnUp];
        nw.data("dtn", "nw").data("dgl", se).drag(...resizeFns).hide();
        w.data("dtn", "w").data("dgl", e).drag(...resizeFns).hide();
        sw.data("dtn", "sw").data("dgl", ne).drag(...resizeFns).hide();
        n.data("dtn", "n").data("dgl", s).drag(...resizeFns).hide();
        s.data("dtn", "s").data("dgl", n).drag(...resizeFns).hide();
        ne.data("dtn", "ne").data("dgl", sw).drag(...resizeFns).hide();
        e.data("dtn", "e").data("dgl", w).drag(...resizeFns).hide();
        se.data("dtn", "se").data("dgl", nw).drag(...resizeFns).hide();

        // drop active path elements
        attr = {
            stroke: "#1DC967",
            "stroke-width": 2
        };
        this.dropNw = paper.path("").attr({...attr}).hide();
        this.dropNe = paper.path("").attr({...attr}).hide();
        this.dropSw = paper.path("").attr({...attr}).hide();
        this.dropSe = paper.path("").attr({...attr}).hide();

        // 虚线激活状态
        this.dashOuterPath = paper.path("").attr("stroke-dasharray", "2 2").hide();

        // 圈选操作
        this.groupSelection = this.renderRect(0, 0, 0, 0, 4).attr({
            fill: "transparent",
            cursor: "move",
            "stroke-width": 2,
            "stroke-dasharray": "8 8"
        }).hide();
        this.dragableGroupSelection();
        this.groupSelection.click(function (evt) {
            me.showGroupSelectionTool();
            eventStop(evt);
        });

        let lineAttr = {
            stroke: this.themeColor,
            "stroke-width": 2,
            opacity: .5,
            "stroke-dasharray": "12 12"
        };
        // 水平对齐线使用rect替代，只需要更新y即可
        this.horizontalLine = this.renderRect(0, 0, 0, 0.0001).attr({...lineAttr}).hide();

        // 垂直对齐线使用rect替代，只需要更新x即可
        this.verticalLine = this.renderRect(0, 0, 0.0001, 0).attr({...lineAttr}).hide();

        // 连线矩形（解决连线选择难问题）
        this.connectRect = this.renderRect(0, 0, 0, 0).attr({
            fill: "transparent",
            "stroke-width": 1,
            stroke: this.themeColor,
            opacity: .8,
            "stroke-dasharray": "2 2"
        }).hide();
        this.connectRect.hover(() => {
        }, function () {
            this.hide();
        }).click(function (e) {
            eventStop(e);
            let target = this.data("target");
            if (target) {
                me.handleClickElement(target, e);
            }
        }).dblclick(function (e) {
            eventStop(e);
            let target = this.data("target");
            if (target) {
                me.handleDblclickElement(target, e);
            }
        });

        // 再根目录上弹出菜单控件
        if (this.popupMenu) {
            this.popupMenu.remove();
        }
        this.popupMenu = new PopupMenuHtmlElementData(createDomElement("div", this.dom)).attr({
            x: 0,
            y: 0,
            width: 100,
            height: "auto",
            color: this.themeColor
        }).hide();

        let imageArgs = [0, 0, 16, 16];

        // 修改类型
        let exchange = this.exchange;
        if (exchange) {
            exchange.remove();
        }
        exchange = this.exchange = this.renderHtmlNode("exchange", ...imageArgs).attr({
            opacity: .9,
            title: "修改类型",
            cursor: "pointer"
        }).mouseover(function () {
            me.dragingElement = {};
        }).mouseout(function () {
            me.dragingElement = null;
        }).hide();
        exchange.click(function (evt) {
            // create next task
            // me.exchangeType();
            let target = exchange.data("from");
            me.exchangePopupMenu(target, evt);
        });

        // 工具栏
        // 连线工具（图片）
        let linkTool = this.linkTool = paper.image(sequenceflow, ...imageArgs).hide();
        linkTool.attr({
            opacity: .5,
            title: "拖拽到目标节点完成连线"
        }).mouseover(function () {
            this.attr("opacity", 1);
        }).mouseout(function () {
            this.attr("opacity", .5);
        });
        // 绑定事件 当拖动到可接受的节点时生成一个连线
        linkTool.drag(
            function (dx, dy, x, y, e) {
                // move
                me.linkToolOnDragMove(this, dx, dy, x, y, e);
            },
            // start
            function () {
                me.dragingElement = linkTool;
            },
            function () {
                // up
                me.linkToolOnDragUp(this);
                me.dragingElement = null;
            });

        // 快速追加下一个任务
        this.nextTaskTool = paper.image(task, ...imageArgs).attr({
            opacity: .5,
            title: "快速追加下一个任务",
            cursor: "pointer"
        }).mouseover(function () {
            this.attr("opacity", 1);
            me.dragingElement = {};
        }).mouseout(function () {
            this.attr("opacity", .5);
            me.dragingElement = null;
        }).hide();
        this.nextTaskTool.click(function (evt) {
            // create next task
            me.nextNode();
        });

        // 快速追加分支任务
        let nextSplitTool = this.nextSplitTool = paper.image(split, ...imageArgs).attr({
            opacity: .5,
            title: "快速追加分支任务",
            cursor: "pointer"
        }).mouseover(function () {
            this.attr("opacity", 1);
            me.dragingElement = {};
        }).mouseout(function () {
            this.attr("opacity", .5);
            me.dragingElement = null;
        }).hide();
        nextSplitTool.click(function (evt) {
            // create next task
            me.nextSplit();
        });

        // 快速追加结束任务
        let nextEndTool = this.nextEndTool;
        if (nextEndTool) {
            nextEndTool.remove();
        }
        nextEndTool = this.nextEndTool = this.renderHtmlNode("end", ...imageArgs).attr({
            opacity: .5,
            title: "快速追加结束任务",
            cursor: "pointer"
        }).mouseover(function () {
            this.attr("opacity", 1);
            me.dragingElement = {};
        }).mouseout(function () {
            this.attr("opacity", .5);
            me.dragingElement = null;
        }).hide();
        nextEndTool.click(function (evt) {
            // create next task
            me.nextEnd();
        });

        if (this.deleteTool) {
            this.deleteTool.remove();
        }
        this.deleteTool = this.renderHtmlNode("del", ...imageArgs).attr({
            opacity: .5,
            title: "删除元素",
            cursor: "pointer"
        }).mouseover(function () {
            this.attr("opacity", 1);
            me.dragingElement = {};
        }).mouseout(function () {
            this.attr("opacity", .5);
            me.dragingElement = null;
        }).hide();
        this.deleteTool.click(function (evt) {
            let host = me.deleteTool.data("host");
            if (!host) {
                me.deleteGroupSelectElements();
            } else {
                // delete element
                me.deleteSelectElement();
            }
            me.deleteTool.hide();
        });
    };

    exchangePopupMenu(target, evt) {
        if (!target) return;
        // get mouse pos relative root dom
        let me = this;
        let {pageX, pageY} = evt;
        let {left, top, right} = this.dom.getBoundingClientRect();
        let x = pageX - left, y = pageY - top;
        let nodeType = target.nodeType;
        let menuData = [];
        let excludeTypes = me.option.excludeTypes || [];
        if (nodeType == NodeTypes.Split) {
            menuData = [
                {type: "XOR", text: "独占"},
                {type: "OR", text: "条件"},
                {type: "AND", text: "并行"}
            ].map(record => {
                let {type} = record;
                return {
                    ...record,
                    action: () => {
                        target.gateway = type;
                        me.hidePopupMenu(0);
                    }
                }
            })
        } else {
            menuData = [
                {type: NodeTypes.Business, text: "业务节点"},
                {type: NodeTypes.Service, text: "服务节点"},
                {type: NodeTypes.Message, text: "消息节点"},
                {type: NodeTypes.Manual, text: "手工节点"}
            ].filter(record => record.type != nodeType && !excludeTypes.includes(record.type.toLowerCase())).map(record => {
                let {type} = record;
                return {
                    ...record,
                    action: () => {
                        target.nodeType = type;
                        me.hidePopupMenu(0);
                    }
                }
            })
        }
        let {width} = this.popupMenu.attrs;
        if (pageX > right - width) {
            x = x - width - 20;
        }
        this.popupMenu.setData(menuData).attr({
            x: x + 10,
            y: y - 5
        }).show();
    }

    /**
     * 圈选拖动处理
     */
    dragableGroupSelection() {
        let me = this;
        let target = this.groupSelection;
        let dragContext = {ox: 0, oy: 0, dx: 0, dy: 0};
        target.drag((dx, dy) => {
            // move
            if (!me.option.editable) return;
            let scaleValue = me.scaleValue || 1;
            dx /= scaleValue;
            dy /= scaleValue;
            let location = {
                x: dragContext.ox + dx,
                y: dragContext.oy + dy
            };
            target.attr(location);
            let panX = dx - dragContext.dx;
            let panY = dy - dragContext.dy;
            dragContext.dx = dx;
            dragContext.dy = dy;
            // update select elements position
            me.elementsPanTo(this.groupSelectElements, panX, panY);
        }, () => {
            // start down
            if (!me.option.editable) return;
            me.dragingElement = target;
            // storing original coordinates
            dragContext.ox = target.attr("x");
            dragContext.oy = target.attr("y");
            target.attr({
                opacity: .8
            });
            target.attr("cursor", "move");
            me.hideEditElements(me.selectElement);
            // 记录undo快照
            if (me.enableHistory()) {
                dragContext.undoData = JSON.stringify(me.getData());
            }
            return false;
        }, () => {
            //end up
            if (!me.option.editable) return;
            me.dragingElement = null;
            target.attr({opacity: 1});
            if (me.enableHistory()) {
                let data = JSON.stringify(me.getData());
                let undoData = dragContext.undoData;
                me.addAction({undo: () => me.setData(undoData), redo: () => me.setData(data)});
            }
            dragContext = {ox: 0, oy: 0, dx: 0, dy: 0};
        });
    };

    /**
     * 追加任务
     */
    nextNode() {
        let fromNode = this.nextTaskTool.data("from");
        if (!fromNode) return;
        let {x, y, width, height} = fromNode.attrs;
        let centerY = y + height / 2;
        let nextNode = this.createBusinessNode(x + width + 150, y);
        // nextNode.data("nodeType", NodeTypes.Business);
        let {height: h2} = nextNode.attrs;
        // 对齐
        nextNode.attr({y: centerY - h2 / 2});
        this.updateElements(nextNode);
        // 创建连线
        let path = this.createPath(fromNode, nextNode);
        this.hideEditElements(path);
    };

    /**
     * 追加分支
     */
    nextSplit() {
        let fromNode = this.nextSplitTool.data("from");
        if (!fromNode) return;
        let isAnd = fromNode.data("gateway") == "AND";
        let {x, y, width, height} = fromNode.attrs;
        let centerY = y + height / 2;

        let nextOneNode = this.createBusinessNode(x + width + 150, y);
        let {height: h1} = nextOneNode.attrs;
        // 对齐
        nextOneNode.attr({y: centerY - h1 / 2 - 100});
        this.updateElements(nextOneNode);
        // 创建连线1
        let connect = this.createPath(fromNode, nextOneNode);
        if (!isAnd) {
            this.setConnectType(connect, this.option.defaultConditionType || "Script");
        }
        this.hideEditElements(connect);

        let nextTwoNode = this.createBusinessNode(x + width + 150, y);
        let {height: h2} = nextTwoNode.attrs;
        // 对齐
        nextTwoNode.attr({y: centerY - h2 / 2 + 100});
        this.updateElements(nextTwoNode);
        // 创建连线2
        let connect2 = this.createPath(fromNode, nextTwoNode);
        if (!isAnd) {
            this.setConnectType(connect2, this.option.defaultConditionType || "Script");
        }
        this.hideEditElements(connect2);
    };

    nextEnd() {
        let fromNode = this.nextTaskTool.data("from");
        if (!fromNode) return;
        let {x, y, width, height} = fromNode.attrs;
        let centerY = y + height / 2;
        let nextNode = this.createEndNode(x + width + 150, y);
        let {height: h2} = nextNode.attrs;
        // 对齐
        nextNode.attr({y: centerY - h2 / 2});
        // 创建连线
        this.createPath(fromNode, nextNode);
    };

    /**
     * 设置样式
     */
    setWrapperStyle(style) {
        let container = this.flowWrapper;
        if (style && typeof style == 'object') {
            assign(container.style, style);
        }
    };

    /**
     * 初始化样式
     * */
    initStyles() {
        let parentStyle = {
            position: "relative",
            overflow: this.option.overflow || "hidden"
        };
        if (this.option.background) {
            parentStyle.background = `${this.option.background}`;
        } else if (this.option.grid) {
            // 如果网格背景
            parentStyle.background = `url("${bg}")`;
        }
        // 设置bg图片
        // this.setContainerStyle(parentStyle);
        assign(this.dom.style, parentStyle);
    };

    // settings
    get settings() {
        return this.option.settings || {};
    };

    // theme color
    get themeColor() {
        return this.settings.themeColor;
    };

    /** 初始化菜单 */
    initMenu(menuDom) {
        let me = this;
        this.menu = menuDom;
        assign(menuDom.style, {
            position: "absolute",
            left: 0,
            top: 0,
            width: "60px",
            display: "flex",
            flexDirection: "column",
            alignItems: "center",
            fontSize: "14px",
            color: this.themeColor,
            padding: "5px 5px 20px",
            background: "hsla(0,0%,100%,.9)",
            boxShadow: "0 1px 4px rgba(0,0,0,.3)",
            userSelect: "none",
        });

        // 拖动上下文
        const dragContext = {
            dragmenu: false,
            type: null,
            element: null,
        };

        /** 拖动初始化 */
        const onDragStart = (event, domHandle) => {
            const {pageX, pageY} = event;

            let dragmenu = domHandle == menuDom;
            if (!dragmenu) {
                // 当前拖拽类型menu-item
                let type = domHandle.dataset.type;
                dragContext.type = type;
            }
            // 记录
            dragContext.px = pageX;
            dragContext.py = pageY;

            let {x, y, left, top} = me.dom.getBoundingClientRect()
            let offsetX = x || left;
            let offsetY = y || top;
            dragContext.offsetX = offsetX;
            dragContext.offsetY = offsetY;
            dragContext.dragmenu = dragmenu;

            if (dragmenu) {
                dragContext.left = Number(menuDom.style.left.replace("px", ""));
                dragContext.top = Number(menuDom.style.top.replace("px", ""));
            }
        }

        /**
         * 拖动中处理
         *
         * @param event
         */
        const onDragMove = (event) => {
            let {type, element, dragmenu, left, top} = dragContext;
            const {pageX, pageY} = event;
            let dx = pageX - dragContext.px;
            let dy = pageY - dragContext.py;
            if (dragmenu) {
                let newLeft = left + dx <= 0 ? 0 : left + dx;
                let newTop = top + dy <= 0 ? 0 : top + dy;
                // 更新菜单的位置
                assign(menuDom.style, {
                    left: `${newLeft}px`,
                    top: `${newTop}px`,
                });
            } else {
                if (!element) {
                    let x = dragContext.px - dragContext.offsetX - 20,
                        y = dragContext.py - dragContext.offsetY - 20;

                    let scaleValue = this.scaleValue || 1;
                    x = (x - me.offsetX) / scaleValue
                    y = (y - me.offsetY) / scaleValue

                    // 获取初始位置
                    if (type == "manual") {
                        dragContext.element = element = me.createManualNode(x, y);
                    } else if (type == "service") {
                        dragContext.element = element = me.createServiceNode(x, y);
                    } else if (type == "businessTask") {
                        dragContext.element = element = me.createBusinessNode(x, y);
                    } else if (type == "message") {
                        dragContext.element = element = me.createMessageNode(x, y);
                    } else if (type == "start") {
                        dragContext.element = element = me.createStartNode(x, y);
                    } else if (type == "end") {
                        dragContext.element = element = me.createEndNode(x, y);
                    } else if (type == "xor") {
                        dragContext.element = element = me.createSplitNode(x, y);
                        element.data("gateway", "XOR");
                    } else if (type == "or") {
                        dragContext.element = element = me.createSplitNode(x, y);
                        element.updateHTML(DefaultHtmlTypes["or"]);
                        element.data("gateway", "OR");
                    } else if (type == "and") {
                        dragContext.element = element = me.createSplitNode(x, y);
                        element.updateHTML(DefaultHtmlTypes["and"]);
                        element.data("gateway", "AND");
                    } else if (type == "join") {
                        dragContext.element = element = me.createJoinNode(x, y);
                    }

                    me.handleClickElement(element, event);
                    me.selectElement = element;
                    me.elementDragStart(element);
                }
                // 更新element的位置
                me.elementDragMove(element, dx, dy);
            }
        }

        /**
         * 拖动结束
         *
         */
        const onDragUp = (event) => {
            if (dragContext.element) {
                let element = dragContext.element;
                element.attr({opacity: 1});
                // 支持撤销
                me.handleElementAction(element);
            }
            delete dragContext.type;
            delete dragContext.element;
            delete dragContext.dragmenu;
            me.hideAlignLines();
            removeEventListener("mousemove", onDragMove);
            removeEventListener("mouseup", onDragUp);
        }

        // 支持菜单拖拽移动
        if (typeof this.option.menu == "object" && this.option.menu.draggable) {
            assign(menuDom.style, {
                cursor: "move"
            });
            // 拖动处理
            bindDomEvent(menuDom, "mousedown", function (event) {
                onDragStart(event, menuDom);
                addEventListener("mousemove", onDragMove);
                addEventListener("mouseup", onDragUp);
                eventStop(event);
            });
        }

        // 设置item背景图片
        menuDom.querySelectorAll(".menu-item").forEach(item => {
            let type = item.dataset.type;
            let width = 36 * 8 / 9, height = 36 * 8 / 9;
            if (type == "or" || type == "xor" || type == "and" || type == "join") {
                width = height = 45 * 8 / 9;
            }
            assign(item.style, {
                width: `${width}px`,
                height: `${height}px`,
                margin: "4px 0"
            });

            let excludeTypes = me.option.excludeTypes || [];
            if(excludeTypes.includes(type)) {
                assign(item.style, {
                    display: `none`
                });
            }

            if (type == "select") {
                // 绑定全选事件
                item.style.cursor = `pointer`;
                item.innerHTML = DefaultHtmlTypes["select"];
                // 点击处理
                bindDomEvent(item, "click", function (event) {
                    me.groupSelectionFlag = true;
                    me.paper.node.style.cursor = "crosshair";
                    eventStop(event);
                });

            } else if (type == "reset") {
                item.style.cursor = `pointer`;
                item.innerHTML = DefaultHtmlTypes["reset"];
                bindDomEvent(item, "mousedown", function (event) {
                    eventStop(event);
                });
                // 点击处理
                bindDomEvent(item, "click", function (event) {
                    me.reset();
                    eventStop(event);
                });
            } else if (type == "exp") {
                item.style.cursor = `pointer`;
                //item.style.color = this.themeColor;
                item.innerHTML = DefaultHtmlTypes["exp"];
                // 点击处理
                bindDomEvent(item, "mousedown", function (event) {
                    eventStop(event);
                });
                // 点击处理
                bindDomEvent(item, "click", function (event) {
                    me.exportJSON();
                    eventStop(event);
                });
            } else if (type == "imp") {
                item.style.cursor = `pointer`;
                //item.style.color = this.themeColor;
                item.innerHTML = DefaultHtmlTypes["imp"];
                bindDomEvent(item, "mousedown", function (event) {
                    eventStop(event);
                });
                // 点击处理
                bindDomEvent(item, "click", function (event) {
                    me.handleImport();
                    eventStop(event);
                });
            } else if (type == "picture") {
                item.style.cursor = `pointer`;
                //item.style.color = this.themeColor;
                item.innerHTML = DefaultHtmlTypes["picture"];
                bindDomEvent(item, "mousedown", function (event) {
                    eventStop(event);
                });
                // 点击处理
                bindDomEvent(item, "click", function (event) {
                    me.exportImage();
                    eventStop(event);
                });
            } else {
                assign(item.style, {
                    cursor: "move"
                });
                // item.style.color = this.themeColor;
                item.innerHTML = DefaultHtmlTypes[type];
                // 拖动处理
                bindDomEvent(item, "mousedown", function (event) {
                    onDragStart(event, item);
                    addEventListener("mousemove", onDragMove);
                    addEventListener("mouseup", onDragUp);
                    eventStop(event);
                });
            }
        });
    };

    /**
     * 添加历史活动
     *
     * @param action
     */
    addAction(action) {
    };

    /**
     * 设置菜单的样式
     *
     * @param style
     */
    setMenuStyle(style) {
        if (typeof style != 'object') {
            style = {};
        }
        let menu = this.menu;
        if (menu) {
            assign(menu.style, style || {});
        }
    };

    /**
     * 设置工具栏的样式
     *
     * @param style
     */
    setToolsStyle(style) {
        if (typeof style != 'object') {
            style = {};
        }
        let flowToolsDom = this.flowToolsDom;
        if (flowToolsDom) {
            assign(flowToolsDom.style, style || {});
        }
    };

    /** 初始化input */
    initInput(inputDom) {
        let me = this;
        this.input = inputDom;
        bindDomEvent(inputDom, "input", function (evt) {
            let value = evt.target.innerText.replace(/[\s\r\n]/g, '');
            me.updateActiveText(value, false);
        });

        bindDomEvent(inputDom, "blur", function (evt) {
            // console.log(" blur ", evt.target.innerText.replace(/[\s\r\n]/g, ''));
            me.endInputEdit();
        });

        // 鼠标按下拦截组织冒泡平移事件
        bindDomEvent(inputDom, "mousedown", function (evt) {
            me.disablePan = true;
        });

        this.textElement = null;
    };

    // // 弹出框设计
    // initPopwin(popwinDom) {
    //     let me = this;
    //     this.popwin = popwinDom;
    //     assign(popwinDom.style, {
    //         // display: "none",
    //         top: "150px",
    //         padding: "0 20px 20px",
    //         minHeight: "350px",
    //         display: "none"
    //     });
    //
    //     // 关闭事件
    //     bindDomEvent(popwinDom.querySelector(".close-handle"), "click", function () {
    //         me.closePopwin();
    //     });
    //
    //     // mousedown事件阻止冒泡（传播）
    //     bindDomEvent(popwinDom, "mousedown", function (evt) {
    //         evt.stopPropagation();
    //     });
    //
    //     // 表单dom
    //     // this.propertyForm = popwinDom.querySelector(".flow-property-form");
    // };

    /** 打开元素的属性编辑窗口 */
    openElementPropertyPop(element) {
        if (!this.option.editable) return;
//         let me = this;
//         let propertyModels = [];
//         // 双击空白区域可维护流程信息
//         if (!element) {
//             propertyModels.push({
//                 label: "流程标识",
//                 value: me.processId || "",
//                 event: "input",
//                 key: "processId",
//                 html: `<input data-key="processId" placeholder="流程id"/>`,
//                 callback(value) {
//                     me.processId = value;
//                 }
//             }, {
//                 label: "流程名称",
//                 value: me.processName || "",
//                 event: "input",
//                 key: "processName",
//                 html: `<input data-key="processName" placeholder="流程名称"/>`,
//                 callback(value) {
//                     me.processName = value;
//                 }
//             });
//         } else {
//             let type = element.type;
//             let nodeType = element.data("nodeType");
//             propertyModels = [
//                 {
//                     label: "元素ID",
//                     value: element.id,
//                     event: "input",
//                     readonly: true,
//                     key: "id",
//                     html: `<input data-key="id"/>`,
//                 }
//             ];
//             if (type == "path") {
//                 let fromElementOutPaths = element.data("from").data("out");
//                 let uniqueConnect = keys(fromElementOutPaths).length == 1;
//                 propertyModels.push({
//                     label: "元素名称",
//                     value: element.data("text").attr("text") || "",
//                     event: "input",
//                     key: "name",
//                     html: `<input data-key="name" placeholder="请输入元素名称"/>`,
//                     callback(value) {
//                         me.setElementName(element, value);
//                     }
//                 }, {
//                     label: "连线样式",
//                     value: element.data("pathStyle") || "",
//                     event: "input",
//                     key: "pathStyle",
//                     html: `<select data-key="pathStyle">
//                            <option value="broken">折线</option>
//                            <option value="straight">直线</option>
// <!--                           <option value="h2v">h2v</option>-->
// <!--                           <option value="v2h">v2h</option>-->
// <!--                           <option value="curve">曲线</option>-->
//                        </select>`,
//                     callback(value) {
//                         if (value != element.data("pathStyle")) {
//                             me.resetConnectPathData(element, value);
//                             element.data("pathStyle", value);
//                         }
//                         // update path
//                     }
//                 }, {
//                     label: "分支策略",
//                     value: element.data("conditionType") || "Script",
//                     event: "change",
//                     // readonly: uniqueConnect,
//                     key: "conditionType",
//                     html: `<select data-key="conditionType">
//                            <option value="Always">Always</option>
//                            <option value="Script">Script</option>
//                            <option value="HandlerCall">HandlerCall</option>
//                        </select>`,
//                     callback(value) {
//                         me.setConnectType(element, value);
//                     }
//                 }, {
//                     label: "脚本表达式",
//                     value: element.data("script") || "",
//                     event: "input",
//                     key: "script",
//                     html: `<textarea data-key="script"></textarea>`,
//                     readonly: uniqueConnect,
//                     callback(value) {
//                         element.data("script", value);
//                     }
//                 }, {
//                     label: "优先级",
//                     value: element.data("priority") || 0,
//                     event: "input",
//                     key: "priority",
//                     html: `<input data-key="priority" type="number"/>`,
//                     readonly: uniqueConnect,
//                     callback(value) {
//                         element.data("priority", !value ? 0 : Number(value));
//                     }
//                 });
//             } else {
//                 // 节点
//                 switch (nodeType) {
//                     case "Start":
//                     case "End":
//                     case "Join": {
//                         break;
//                     }
//                     case "Split": {
//                         propertyModels.push({
//                             label: "网关类型",
//                             value: element.data("gateway") || "XOR",
//                             event: "change",
//                             key: "gateway",
//                             html: `<select data-key="gateway">
//                                <option value="XOR">条件</option>
//                                <option value="OR">条件并行</option>
//                                <option value="AND">并行</option>
//                            </select>`,
//                             callback(value) {
//                                 element.updateHTML(DefaultHtmlTypes[value.toLowerCase()]);
//                                 element.data("gateway", value);
//                             }
//                         });
//                         break;
//                     }
//                     default: {
//                         let handler = element.data("handler");
//                         if (!handler) {
//                             element.data("handler", handler = {});
//                         }
//                         if (element.data("text")) {
//                             propertyModels.push({
//                                 label: "元素名称",
//                                 value: element.data("text").attr("text") || "",
//                                 event: "input",
//                                 key: "name",
//                                 html: `<input data-key="name" placeholder="请输入元素名称"/>`,
//                                 callback(value) {
//                                     element.data("text").attr("text", value);
//                                 }
//                             })
//                         }
//                         propertyModels.push({
//                             label: "任务类型",
//                             value: element.data("nodeType") || "Business",
//                             event: "change",
//                             key: "nodeType",
//                             html: `<select data-key="nodeType">
//                                <option value="Business">Business</option>
//                                <option value="Service">Service</option>
//                                <option value="Script">Script</option>
//                                <option value="Manual">Manual</option>
//                            </select>`,
//                             callback(value) {
//                                 element.data("nodeType", value);
//                             }
//                         }, {
//                             label: "是否异步执行",
//                             value: handler.asynchronous || false,
//                             event: "change",
//                             key: "asynchronous",
//                             checkbox: true,
//                             html: `<input data-key="asynchronous" type="checkbox">`,
//                             callback(value) {
//                                 handler.asynchronous = value;
//                             }
//                         }, {
//                             label: "超时设置",
//                             value: handler.timeout || 0,
//                             event: "input",
//                             key: "timeout",
//                             html: `<input type="number" data-key="timeout" placeholder="单位为毫秒"/>`,
//                             callback(value) {
//                                 handler.timeout = !value ? 0 : Number(value);
//                             }
//                         }, {
//                             label: "延迟设置",
//                             value: handler.delay || 0,
//                             event: "input",
//                             key: "delay",
//                             html: `<input data-key="delay" type="number" placeholder="单位为毫秒"/>`,
//                             callback(value) {
//                                 handler.delay = !value ? 0 : Number(value);
//                             }
//                         }, {
//                             label: "循环迭代次数",
//                             value: handler.iterate || 0,
//                             event: "input",
//                             key: "iterate",
//                             html: `<input data-key="iterate" type="number"/>`,
//                             callback(value) {
//                                 handler.iterate = !value ? 0 : Number(value);
//                             }
//                         }, {
//                             label: "失败策略",
//                             value: handler.policy || "Stop",
//                             event: "change",
//                             key: "policy",
//                             html: `<select data-key="policy">
//                                        <option value="Stop">终止</option>
//                                        <option value="Continue">继续</option>
//                                    </select>`,
//                             callback(value) {
//                                 handler.policy = value;
//                             }
//                         });
//                     }
//                 }
//             }
//         }
//
//         let htmlCodes = [];
//         let propertyModelMap = {}
//         for (let propertyModel of propertyModels) {
//             let {label, html, key} = propertyModel;
//             htmlCodes.push(`
//                  <div class="flow-form-item">
//                     <div>${label}</div>
//                     ${html}
//                  </div>
//             `);
//             propertyModelMap[key] = propertyModel;
//         }
//         this.propertyForm.innerHTML = htmlCodes.join("");
//         // 样式设置,事件处理
//         this.propertyForm.querySelectorAll(".flow-form-item").forEach(item => {
//             assign(item.style, {
//                 display: "flex",
//                 flexDirection: "column",
//                 margin: "8px",
//                 lineHeight: "28px"
//             });
//
//             let inputDom = item.children[1];
//             assign(inputDom.style, {
//                 height: "28px",
//                 lineHeight: "28px",
//                 border: "1px solid #dcdfe6"
//             });
//             let key = inputDom.dataset.key;
//             let propertyModel = propertyModelMap[key];
//
//             if (propertyModel.checkbox) {
//                 if (propertyModel.value) {
//                     inputDom.checked = true;
//                 }
//             } else {
//                 inputDom["value"] = propertyModel.value;
//             }
//             if (propertyModel.readonly) {
//                 inputDom.setAttribute("disabled", "disabled");
//             } else {
//                 bindDomEvent(inputDom, propertyModel.event, function (evt) {
//                     let value = null;
//                     if (propertyModel.checkbox) {
//                         value = !!evt.target.checked;
//                     } else {
//                         value = evt.target.value;
//                     }
//                     propertyModel.callback(value);
//                 });
//             }
//         });
//         this.popwin.style.display = "block";
//         this.disablePan = true;
    };

    // 重置连线的风格
    resetConnectPathData(connectElement, pathStyle) {
        let fromElement = connectElement.data("from");
        let toElement = connectElement.data("to");
        this.resetPathData(connectElement, fromElement, toElement, pathStyle);
    };

    // // 关闭弹出层窗口
    // closePopwin() {
    //     if (this.popwin) {
    //         this.popwin.style.display = "none";
    //         this.disablePan = false;
    //     }
    // };

    // begin edit text
    beginInputEdit(element) {
        if (!this.option.editable) return;
        let input = this.input;
        if (element.data("text")) {
            let textElement = element.data("text");
            this.textElement = textElement;
            let textValue = textElement.attr("text");
            // 位置
            let {x, y} = textElement.attrs;
            // 给input赋值
            input.innerHTML = textValue;
            // 设置input的位置
            assign(input.style, {
                // left: x + this.translateX + "px",
                // top: y + this.translateY + "px",
                left: x + "px",
                top: y + "px",
                display: "block"
            });
            input.focus();
            textElement.hide();
        }
    };

    // 结束编辑
    endInputEdit() {
        // let value = this.input.innerText.replace(/[\s\r\n]/g, '');
        this.input.innerText = "";
        this.input.style.display = "none";
        let textElement = this.textElement;
        if (textElement) {
            textElement.show();
        }
        this.disablePan = false;
    };

    // 导入初始化
    initFileInput(fileInput) {
        let me = this;
        this.fileInput = fileInput;
        bindDomEvent(fileInput, "change", (evt) => {
            me.onImportFile(evt);
        });
    };

    /**
     * 初始化
     */
    initData() {
        this.elements = {};
        this.containers = {};
        this.groupSelectElements = [];
    };

    initPaper() {
        let paperNode = this.paper.node;
        paperNode.querySelector("defs").innerHTML = defs;
        for (let color of (this.connectColors || [])) {
            createColorMarker(paperNode, color);
        }
    };

    /**
     *
     */
    init() {
        // 鼠标点击空地隐藏
        let me = this;
        let paperSvgNode = me.paper.node;
        // 单击事件
        bindDomEvent(paperSvgNode, "click", (evt) => me.handleClickBlank(evt));
        // 右键事件
        bindDomEvent(paperSvgNode, "contextmenu", (evt) => me.handleContextmenu(evt));
        // 绑定双击事件
        bindDomEvent(paperSvgNode, "dblclick", (evt) => me.handleDblClickBlank(evt));
        // 键盘事件
        this.handleKeyboardEvents();
        // 根节点的鼠标拖动事件
        this.handleMouseDragMoveEvents();

        // 是否支持缩放
        this.setScaleable(this.option.zoomable);
        if (this.option.zoomable) {
            let me = this;
            let flowToolsDom = this.dom.querySelector(".flow-tools");
            this.flowToolsDom = flowToolsDom;
            assign(flowToolsDom.style, {
                position: "absolute",
                right: "5px",
                top: "75%",
                transform: "translate(0, -50%)",
                width: "48px",
                display: "flex",
                flexDirection: "column",
                alignItems: "center",
                fontSize: "14px",
                color: this.themeColor,
                padding: "5px",
                userSelect: "none",
            });

            // 设置item背景图片
            flowToolsDom.querySelectorAll(".tool-item").forEach(item => {
                let type = item.dataset.type;
                let width = 36, height = 36;
                assign(item.style, {
                    width: `${width}px`,
                    height: `${height}px`,
                    margin: "2px 0",
                    cursor: "pointer",
                    background: "hsla(0,0%,100%,.9)",
                    boxShadow: "0 1px 4px rgba(0,0,0,.3)",
                });
                // item.style.color = this.themeColor;
                item.innerHTML = DefaultHtmlTypes[type];

                // stop propagation
                bindDomEvent(item, "mousedown", function (event) {
                    eventStop(event);
                });

                // zoom处理
                bindDomEvent(item, "click", function (event) {
                    eventStop(event);
                    if (type == "overview") {
                        me.overview();
                    } else if (type == "zoomReset") {
                        me.zoomReset();
                        // chrome文本位置有兼容问题
                        if (isChrome) {
                            me.triggerTextRefreshPos();
                        }
                    } else if (type == "zoomIn") {
                        me.zoomIn();
                        // chrome文本位置有兼容问题
                        if (isChrome) {
                            me.triggerTextRefreshPos();
                        }
                    } else if (type == "zoomOut") {
                        me.zoomOut();
                        // chrome文本位置有兼容问题
                        if (isChrome) {
                            me.triggerTextRefreshPos();
                        }
                    }

                });
            });
        }
    };

    // 处理键盘事件
    handleKeyboardEvents() {
        let me = this;
        if (!this.handleDocumentKeyDown) {
            this.handleDocumentKeyDown = (e) => {
                if (e.keyCode == 46) {
                    // delete
                    me.deleteSelectElement();
                } else if (e.keyCode == 8) {
                    // backspace
                    let active = document.activeElement;
                    if (active.getAttribute && active.getAttribute("readonly") == "readonly") {
                        return false;
                    }
                } else if (e.keyCode == 16) {
                    me.shiftMode = true;
                } else if (e.keyCode == 17) {
                    // Control
                    me.resetGroupSelection();
                    me.groupSelectionFlag = true;
                    me.paper.node.style.cursor = "crosshair";
                } else if (e.keyCode == 89) {
                    if (e.ctrlKey) {
                        // ctrl+Y(前进)
                        me.redo();
                    }
                } else if (e.keyCode == 90) {
                    if (e.ctrlKey) {
                        // ctrl+z（后退）
                        me.undo();
                    }
                }
            }
        }
        if (!this.handleDocumentKeyUp) {
            this.handleDocumentKeyUp = (event) => {
                // Control up
                if (event.keyCode == 16) {
                    me.shiftMode = false;
                }
                if (event.keyCode == 17) {
                    me.groupSelectionFlag = false;
                    me.paper.node.style.cursor = "default";
                }
            }
        }
        // 先销毁
        removeEventListener("keydown", this.handleDocumentKeyDown);
        removeEventListener("keyup", this.handleDocumentKeyUp);

        // 键盘事件(在销毁时需要移除)
        addEventListener("keydown", this.handleDocumentKeyDown);
        addEventListener("keyup", this.handleDocumentKeyUp);
    };

    // 鼠标拖拽移动事件
    handleMouseDragMoveEvents() {
        let me = this;
        const canvasDragContext = {
            moved: false
        };
        const onCanvasDragStart = (event) => {
            const {pageX, pageY} = event;
            canvasDragContext.px = pageX;
            canvasDragContext.py = pageY;
            if (me.groupSelectionMode()) {
                // 获取鼠标点击的坐标，设置为圈选的位置
                let {x, y, left, top} = me.dom.getBoundingClientRect();
                let start = {
                    x: (pageX - (x || left || 0)),
                    y: (pageY - (y || top || 0))
                };

                let scaleValue = me.scaleValue || 1;
                start.x = (start.x - me.offsetX) / scaleValue;
                start.y = (start.y - me.offsetY) / scaleValue;

                this.groupSelection.data("start", start);
            } else {
                me.paper.node.style.cursor = "grab";
                canvasDragContext.translateX = me.translateX;
                canvasDragContext.translateY = me.translateY;
            }
        }
        const onCanvasDragMove = (event) => {
            const {pageX, pageY} = event;
            let dx = pageX - canvasDragContext.px;
            let dy = pageY - canvasDragContext.py;
            if (dx * dx + dy * dy > 0) {
                canvasDragContext.moved = true;
            }
            if (me.groupSelectionMode()) {
                let {x, y} = this.groupSelection.data("start");
                let scaleValue = this.scaleValue || 1;
                dx /= scaleValue;
                dy /= scaleValue;
                let width = dx, height = dy;
                if (dx < 0) {
                    x += dx;
                    width = -dx;
                }
                if (dy < 0) {
                    y += dy;
                    height = -dy;
                }
                // update selection rect
                this.groupSelection.attr({
                    x,
                    y,
                    width,
                    height,
                    stroke: this.themeColor
                }).show();
            } else {
                me.translateX = canvasDragContext.translateX + dx;
                me.translateY = canvasDragContext.translateY + dy;
                // me.hideEditElements(this.selectElement);
                me.cancelSelect();
                me.translateTo(me.translateX, me.translateY);
            }
        }
        const onCanvasDragUp = (event) => {
            try {
                // panto and remove transform
                if (me.groupSelectionMode()) {
                    // compute groupSelectElements
                    me.showGroupSelection();
                } else {
                    if (canvasDragContext.moved) {
                        me.panTo(me.translateX / this.scaleValue, me.translateY / this.scaleValue, true);
                    }
                }
                if (!canvasDragContext.moved) {
                    // only click
                    me.resetGroupSelection();
                } else {
                    me.connectRect.hide();
                }
                me.groupSelectionFlag = false;
                canvasDragContext.moved = false;
                me.paper.node.style.cursor = "default";
            } finally {
                removeEventListener("mousemove", onCanvasDragMove);
                removeEventListener("mouseup", onCanvasDragUp);
            }
        }
        // 平移处理
        bindDomEvent(me.dom, "mousedown", function (event) {
            if (!me.dragingElement && !me.disablePan) {
                if (!me.enablePanable() && !me.groupSelectionMode()) {
                    return;
                }
                onCanvasDragStart(event);
                me.endInputEdit();
                addEventListener("mousemove", onCanvasDragMove);
                addEventListener("mouseup", onCanvasDragUp);
                eventStop(event);
            }
        });
    };

    // 刷新文本位置
    triggerTextRefreshPos() {
        let texts = this.paper.node.querySelectorAll("text");
        texts.forEach(text => {
            let x = text.getAttribute("x");
            if (x != undefined) {
                text.setAttribute("x", x);
            }
        });
    };

    /**
     * 是否支持缩放
     *
     * @param zoomable
     */
    setScaleable(zoomable) {
        let me = this;
        if (zoomable) {
            let wheelEventFn = (event) => {
                let data = event.wheelDelta || -event.detail;
                if (data > 0) {
                    // 向上滚 放大
                    me.zoomIn();
                } else {
                    // 向下滚 缩小
                    me.zoomOut();
                }
                eventStop(event);
            }
            this.wheelEventFn = wheelEventFn;
            // 平移处理
            bindDomEvent(me.dom, "wheel", wheelEventFn);
        }
    };

    // 放大
    zoomIn() {
        this.setScale(this.scaleValue + 0.2);
    };

    // 缩小
    zoomOut() {
        this.setScale(this.scaleValue - 0.2);
    };

    // reset zoom
    zoomReset() {
        this.setScale(1);
    };

    setScale(value) {
        // this.translateX = 0;
        // this.translateY = 0;
        if (this.translateX != 0 || this.translateY != 0) {
            // 这里先注释掉
            // this.panTo(this.translateX, this.translateY);
        }
        if (value <= 0.01) {
            value = 0.01;
        }
        this.scaleValue = value;
        this.updateWrapperTransform();
    };

    /**
     * 返回大纲视图（全节点视图）
     */
    overview() {
        let elementValues = values(this.elements);
        // 设置一个矩形边界使所有的节点都在矩形范围内
        let minX = 0, minY = 0;
        let maxEndx = 0, maxEndy = 0, onceFlag = true;
        for (let elementValue of elementValues) {
            let type = elementValue.type;
            if (type == "path") continue;
            let {x, y, width, height} = elementValue.attrs;
            if (onceFlag) {
                onceFlag = false;
                minX = x;
                minY = y;
                maxEndx = x + width;
                maxEndy = y + height;
            } else {
                minX = min(minX, x);
                minY = min(minY, y);
                maxEndx = max(maxEndx, x + width);
                maxEndy = max(maxEndy, y + height);
            }
        }

        let rectX = minX, rectY = minY;
        let elementsBoundingWidth = maxEndx - rectX, elementsBoundingHeight = maxEndy - rectY;
        let {width, height} = this.flowWrapper.parentNode.getBoundingClientRect();
        console.log("flowWrapper parentNode", width, height);
        let center = {
            x: minX + elementsBoundingWidth / 2,
            y: minY + elementsBoundingHeight / 2
        }
        console.log("minX minY", minX, minY);
        let targetCenter = {
            x: width / 2,
            y: height / 2
        }
        let {overviewOffsetWidth: offsetW, overviewOffsetHeight: offsetH} = this.option;
        let viewWidth = width - 2 * offsetW, viewHeight = height - 2 * offsetH;
        let scale = 1;
        if (viewWidth < elementsBoundingWidth || viewHeight < elementsBoundingHeight) {
            scale = min(viewWidth / (elementsBoundingWidth || 1), viewHeight / (elementsBoundingHeight || 1));
        }

        let dx = (targetCenter.x - center.x) * scale;
        let dy = (targetCenter.y - center.y) * scale;

        this.translateTo(dx, dy);
        this.setScale(scale);
    };


    /**
     * 是否支持圈选
     *
     * @returns {boolean|*}
     */
    groupSelectionMode() {
        return this.option.editable && this.groupSelectionFlag;
    };

    /**
     * 是否支持平移
     *
     * @returns {boolean|*}
     */
    enablePanable() {
        return this.option.panable;
    };

    /**
     * 启用历史管理
     */
    enableHistory() {
        return this.option.enableHistory;
    };

    /**
     * 显示圈选的工具栏图标
     */
    showGroupSelectionTool() {
        this.cancelSelect();
        let {x, y, width, height} = this.groupSelection.attrs;
        // this.deleteTool
        let tx = x + width + 5;
        let ty = y - 5;
        this.deleteTool.attr({
            x: tx,
            y: ty
        }).data("host", null).show();
    };

    /**
     * 隐藏圈选的工具栏图标
     */
    hideGroupSelectionTool() {
        this.deleteTool.hide();
    };

    /**
     * 圈选元素
     * */
    showGroupSelection() {
        let groupSelection = this.groupSelection;
        if (!groupSelection || !groupSelection.attrs) return;
        let {x, y, width, height} = groupSelection.attrs;
        if (!x || !y || !width || !height) return;

        let groupSelectElements = this.groupSelectElements = [];
        let elements = this.elements;
        let connectSets = [];
        for (let elementId in elements) {
            let element = elements[elementId];
            let type = element.type;
            if (type != "path") {
                let dropFlag = this.isDropContainer(element, groupSelection);
                if (dropFlag) {
                    groupSelectElements.push(element);
                    let outLines = element.data("out");
                    for (let lineId in outLines || {}) {
                        if (!connectSets.includes(lineId)) {
                            connectSets.push(lineId);
                            groupSelectElements.push(outLines[lineId]);
                        }
                    }
                    let inLines = element.data("in");
                    for (let lineId in inLines || {}) {
                        if (!connectSets.includes(lineId)) {
                            connectSets.push(lineId);
                            groupSelectElements.push(inLines[lineId]);
                        }
                    }
                }
            }
        }

        if (this.groupSelectElements.length == 0) {
            this.groupSelection.hide();
            this.groupSelectionVisible = false;
        } else {
            this.groupSelection.show();
            this.groupSelectionVisible = true;
            // show delete tool
            this.showGroupSelectionTool();
        }
    };

    /** 重置圈选 */
    resetGroupSelection() {
        this.groupSelection.hide();
        this.groupSelectElements = [];
        if (this.groupSelectionVisible) {
            this.hideGroupSelectionTool();
        }
    };

    /**
     * 取消当前选择
     */
    cancelSelect() {
        let selectElement = this.selectElement;
        if (selectElement) {
            this.hideEditElements(selectElement);
            this.selectElement = null;
        }
    };

    // 单击事件
    handleClickBlank(evt) {
        this.option.clickBlank && this.option.clickBlank(evt);
        this.cancelSelect();
        // this.closePopwin();
    };

    // 双击空白
    handleDblClickBlank(evt) {
        this.option.dblclickBlank && this.option.dblclickBlank(evt);
        // 内部编辑
        if (this.option.enablePropertyPop) {
            this.openElementPropertyPop();
        }
    };

    // 右键事件
    handleContextmenu(evt) {
        this.option.onContextMenu && this.option.onContextMenu(evt);
        eventStop(evt);
        return false;
    };

    /**
     * 根据html创建节点（以div作为容器）
     */
    renderHtmlNode(type, x, y, width, height, createFunction) {
        let html;
        if ((html = GlobalHTMLTypes[type]) == undefined || html == null) {
            console.error(`html type [${type}] is not register `);
            return;
        }
        let domEle = createDomElement("div", this.flowWrapper);
        domEle.innerHTML = html;
        domEle.style.position = "absolute";
        let element = typeof createFunction == 'function' ? createFunction(domEle) : new HtmlElementData(domEle)
        element.attr({
            x: x || 0,
            y: y || 0,
            width: width || 0,
            height: height || 0
        });
        return element;
    };

    /**
     * 渲染html文本
     *
     * @param rect
     * @param text
     * @returns {HtmlTextElementData}
     */
    renderHtmlText(x, y, width) {
        let domEle = createDomElement("div", this.flowWrapper);
        domEle.style.position = "absolute";
        let element = new HtmlTextElementData(domEle, !!this.option.nowrap);
        element.attr({x, y});
        // Set width separately
        element.setWidth(width);
        return element;
    };

    /**
     * 绑定uuid（data属性）
     *
     * @param element
     */
    setUUID(element) {
        if (element && this.option.uuid) {
            let val = uuid();
            element.data("uuid", val);
            return val;
        }
        return null;
    };

    /**
     * 绘制通用矩形
     *
     * @param args
     * @returns {*}
     */
    renderRect(...args) {
        return this.paper.rect(...args);
    };

    /**
     * 绘制圆形
     *
     * @param x
     * @param y
     * @param r 半径
     * @returns {*}
     */
    renderCircle(x, y, r) {
        return this.paper.circle(x, y, r);
    };

    /**
     * 绘制图片
     *
     * @param args
     * @returns {*}
     */
    renderImage(...args) {
        return this.paper.image(...args);
    };

    /**
     * 绘制路径
     */
    renderPath(path, attrs) {
        return this.paper.path(path).attr(attrs);
    };

    // get value of y
    getCoordinate(x1, y1, x2, y2, x) {
        if (x2 == x1) {
            console.error("call getCoordinate error for x2 == x1");
            return;
        }
        return x * (y2 - y1) / (x2 - x1) + (y1 * x2 - y2 * x1) / (x2 - x1);
    };

    /**
     * get path data
     *
     * @param elementStart
     * @param elementEnd
     * @returns {{}}
     */
    getPathData(elementStart, elementEnd) {
        let startX = elementStart.attr("x") - 5;
        let startY = elementStart.attr("y") - 5;
        let startWidth = elementStart.attr("width") + 10;
        let startHeight = elementStart.attr("height") + 10;

        let endX = elementEnd.attr("x") - 5;
        let endY = elementEnd.attr("y") - 5;
        let endWidth = elementEnd.attr("width") + 10;
        let endHeight = elementEnd.attr("height") + 10;

        // 计算2个重心连接与2个元素的交点 一共4个交点（分8中情况，8个方位）
        let startCenterX = startX + startWidth / 2;
        let startCenterY = startY + startHeight / 2;

        let endCenterX = endX + endWidth / 2;
        let endCenterY = endY + endHeight / 2;

        let start = {};
        let end = {};
        if (endCenterX < startCenterX && endCenterY < startCenterY) {

            let horizontalStartPoint = {};
            horizontalStartPoint.y = startY;
            horizontalStartPoint.x =
                this.getCoordinate(startCenterY, startCenterX, endCenterY, endCenterX, horizontalStartPoint.y);

            if (horizontalStartPoint.x >= startX) {
                start = horizontalStartPoint;
            } else {
                let verticalStartPoint = {};
                verticalStartPoint.x = startX;
                verticalStartPoint.y = this.getCoordinate(startCenterX, startCenterY,
                    endCenterX, endCenterY, verticalStartPoint.x);
                start = verticalStartPoint;
            }

            let horizontalEndPoint = {};
            horizontalEndPoint.y = endY + endHeight;
            horizontalEndPoint.x = this.getCoordinate(startCenterY, startCenterX,
                endCenterY, endCenterX, horizontalEndPoint.y);
            if (horizontalEndPoint.x >= endX && horizontalEndPoint.x <= endX + endWidth) {
                end = horizontalEndPoint;
            } else {
                let verticalEndPoint = {};
                verticalEndPoint.x = endX + endWidth;
                verticalEndPoint.y = this.getCoordinate(startCenterX, startCenterY,
                    endCenterX, endCenterY, verticalEndPoint.x);
                end = verticalEndPoint;
            }

        } else if (endCenterX < startCenterX && endCenterY == startCenterY) {
            assign(start, {x: startX, y: startCenterY});
            assign(end, {x: endX + endWidth, y: endCenterY});
        } else if (endCenterX < startCenterX && endCenterY > startCenterY) {

            let horizontalStartPoint = {};
            horizontalStartPoint.y = startY + startHeight;
            horizontalStartPoint.x =
                this.getCoordinate(startCenterY, startCenterX, endCenterY, endCenterX, horizontalStartPoint.y);

            if (horizontalStartPoint.x >= startX) {
                start = horizontalStartPoint;
            } else {
                let verticalStartPoint = {};
                verticalStartPoint.x = startX;
                verticalStartPoint.y = this.getCoordinate(startCenterX, startCenterY,
                    endCenterX, endCenterY, verticalStartPoint.x);
                start = verticalStartPoint;
            }

            let horizontalEndPoint = {};
            horizontalEndPoint.y = endY;
            horizontalEndPoint.x = this.getCoordinate(startCenterY, startCenterX,
                endCenterY, endCenterX, horizontalEndPoint.y);
            if (horizontalEndPoint.x >= endX && horizontalEndPoint.x <= endX + endWidth) {
                end = horizontalEndPoint;
            } else {
                let verticalEndPoint = {};
                verticalEndPoint.x = endX + endWidth;
                verticalEndPoint.y = this.getCoordinate(startCenterX, startCenterY,
                    endCenterX, endCenterY, verticalEndPoint.x);
                end = verticalEndPoint;
            }
        } else if (endCenterX == startCenterX && endCenterY < startCenterY) {
            assign(start, {x: startCenterX, y: startY});
            assign(end, {x: endCenterX, y: endY + endHeight});
        } else if (endCenterX == startCenterX && endCenterY > startCenterY) {
            assign(start, {x: startCenterX, y: startY + startHeight});
            assign(end, {x: endCenterX, y: endY});
        } else if (endCenterX > startCenterX && endCenterY < startCenterY) {

            let horizontalStartPoint = {};
            horizontalStartPoint.y = startY;
            horizontalStartPoint.x =
                this.getCoordinate(startCenterY, startCenterX, endCenterY, endCenterX, horizontalStartPoint.y);

            if (horizontalStartPoint.x <= startX + startWidth) {
                start = horizontalStartPoint;
            } else {
                let verticalStartPoint = {};
                verticalStartPoint.x = startX + startWidth;
                verticalStartPoint.y = this.getCoordinate(startCenterX, startCenterY,
                    endCenterX, endCenterY, verticalStartPoint.x);
                start = verticalStartPoint;
            }

            let horizontalEndPoint = {};
            horizontalEndPoint.y = endY + endHeight;
            horizontalEndPoint.x = this.getCoordinate(startCenterY, startCenterX,
                endCenterY, endCenterX, horizontalEndPoint.y);
            if (horizontalEndPoint.x >= endX) {
                end = horizontalEndPoint;
            } else {
                let verticalEndPoint = {};
                verticalEndPoint.x = endX;
                verticalEndPoint.y = this.getCoordinate(startCenterX, startCenterY,
                    endCenterX, endCenterY, verticalEndPoint.x);
                end = verticalEndPoint;
            }
        } else if (endCenterX > startCenterX && endCenterY == startCenterY) {
            assign(start, {x: startX + startWidth, y: endCenterY});
            assign(end, {x: endX, y: endCenterY});
        } else {
            // endCenterX > startCenterX && endCenterY > startCenterY
            let horizontalStartPoint = {};
            horizontalStartPoint.y = startY + startHeight;
            horizontalStartPoint.x =
                this.getCoordinate(startCenterY, startCenterX, endCenterY, endCenterX, horizontalStartPoint.y);

            if (horizontalStartPoint.x <= startX + startWidth) {
                start = horizontalStartPoint;
            } else {
                let verticalStartPoint = {};
                verticalStartPoint.x = startX + startWidth;
                verticalStartPoint.y = this.getCoordinate(startCenterX, startCenterY,
                    endCenterX, endCenterY, verticalStartPoint.x);
                start = verticalStartPoint;
            }

            let horizontalEndPoint = {};
            horizontalEndPoint.y = endY;
            horizontalEndPoint.x = this.getCoordinate(startCenterY, startCenterX,
                endCenterY, endCenterX, horizontalEndPoint.y);
            if (horizontalEndPoint.x >= endX) {
                end = horizontalEndPoint;
            } else {
                let verticalEndPoint = {};
                verticalEndPoint.x = endX;
                verticalEndPoint.y = this.getCoordinate(startCenterX, startCenterY,
                    endCenterX, endCenterY, verticalEndPoint.x);
                end = verticalEndPoint;
            }
        }
        return {data: "M" + start.x + "," + start.y + "L" + end.x + "," + end.y, start, end};
    };

    /**
     * 删除圈选元素
     *
     */
    deleteGroupSelectElements() {
        let me = this;
        let groupSelectElements = this.groupSelectElements;
        if (!groupSelectElements || groupSelectElements.length == 0) return;
        let undoData = null;
        let enableHistory = this.enableHistory();
        if (enableHistory) {
            undoData = JSON.stringify(this.getData());
        }
        for (let element of groupSelectElements) {
            if (element.removed !== true) {
                this.removeElement(element);
            }
        }
        if (enableHistory) {
            let data = JSON.stringify(this.getData());
            this.addAction({
                undo() {
                    me.setData(undoData);
                },
                redo() {
                    me.setData(data);
                }
            });
        }
        this.resetGroupSelection();
    };

    /**
     * 删除选中的元素
     */
    deleteSelectElement() {
        if (this.selectElement) {
            let me = this;
            let element = this.selectElement;

            let action = null;
            let undoData = null;
            if (this.enableHistory()) {
                let {in: inlines = {}, out: outLines = {}} = element.data();
                let type = element.type;
                if (type == "path" || (keys(inlines).length == 0 && keys(outLines).length == 0)) {
                    let elementId = element.id;
                    let elementData = this.toElementData(element);
                    action = {
                        undo() {
                            me.fromElementData(elementData);
                        },
                        redo() {
                            me.deleteElementById(elementId);
                        }
                    };
                    this.addAction(action);
                } else {
                    undoData = JSON.stringify(this.getData());
                }
            }
            this.removeElement(this.selectElement);
            this.selectElement = null;

            if (this.enableHistory() && action == null) {
                let data = JSON.stringify(this.getData());
                this.addAction({
                    undo() {
                        me.setData(undoData);
                    },
                    redo() {
                        me.setData(data);
                    }
                });
            }
        }
    };

    /**
     * 根据id删除元素
     *
     * @param id
     */
    deleteElementById(id) {
        let element = this.getElementById(id);
        if (element) {
            this.removeElement(element);
        }
    };

    removeElement(targetElement) {
        if (this.selectElement == targetElement) {
            this.hideEditElements(targetElement);
        }
        let type = targetElement.type;
        let dataObject = targetElement.data();

        let me = this;
        setTimeout(() => {
            try {
                if (typeof me.option.onRemoveElement == "function") {
                    me.option.onRemoveElement(targetElement);
                }
            } catch (err) {
            }
        }, 0);
        if (type == "path") {
            // 连线，移除箭头、连线中的矩形、文本
            // 移除元素关系
            let fromElement = targetElement.data("from");
            let toElement = targetElement.data("to");

            if (fromElement && fromElement.data("out")) {
                let outLines = fromElement.data("out");
                delete outLines[targetElement.id];
            }

            if (toElement && toElement.data("in")) {
                let inLines = toElement.data("in");
                delete inLines[targetElement.id];
            }
            this.removePathRelationRects(targetElement);
            // targetElement.data("arrow").remove();
            targetElement.data("text").remove();

            let elementId = targetElement.id;
            targetElement.remove();
            this.unregister(elementId);
        } else {
            // 元素，注意需要移除关联的连线
            for (let i in dataObject) {
                let dataProp = dataObject[i];
                // if (i != "container") {
                // }
                dataProp && dataProp.remove && dataProp.remove();
                if ((i == "in" || i == "out") && dataProp && dataProp instanceof Object) {
                    for (let j in dataProp) {
                        this.removeElement(dataProp[j]);
                        delete dataProp[j];
                    }
                }
            }
            let elementId = targetElement.id;

            // 删除元素时与子流程有关的需要特殊解除关系
            // let dataType = dataObject["type"];
            // let container = dataObject["container"];
            // if (dataType == "mutiSubProcess" || dataType == "serviceGroup") {
            //     // 删除的是容器，获取所有的子节点先删除
            //     let containerObj = this.containers[targetElement.id];
            //     let childElements = containerObj.elements;
            //     for (let i in childElements) {
            //         let childElement = childElements[i];
            //         this.removeElement(childElement);
            //     }
            //     this.removeContainer(containerObj);
            // } else {
            // }
            this.unregister(elementId);
            // // 如果删除的元素时包含在container里面的子节点，解除关系
            // if (container) {
            //     // 解除关系
            //     this.unbindElementFromContainer(targetElement, container);
            // }
            // 最后删除元素
            targetElement.remove();
        }


    };

    /** 移除路径关联的rects */
    removePathRelationRects(targetElement) {
        // 比如折线
        let startElement = targetElement && targetElement.data("start");
        if (startElement) {
            let nextElement = startElement.data("right");
            startElement.remove();
            while (nextElement) {
                let temp = nextElement;
                let leftRect = temp.data("leftRect");
                // leftRect.undrag();
                leftRect.remove();
                nextElement = nextElement.data("right");
                // temp.undrag();
                temp.remove();
            }
        }
    };

    /**
     * 绑定选择事件
     *
     * @param targetElement
     */
    bindSelectEvent(targetElement) {
        // 添加单击事件
        let me = this;
        targetElement.click(function (e) {
            // 阻止冒泡
            eventStop(e);
            me.handleClickElement(targetElement, e);
        });

        if (targetElement.data('text')) {
            targetElement.data('text').click(function (e) {
                if (me.option.textEditOnClick) {
                    me.beginInputEdit(targetElement);
                } else {
                    me.handleClickElement(targetElement, e);
                }
                eventStop(e);
            });
            targetElement.data('text').dblclick(function (e) {
                // 点击文本直接修改文本，不再触发双击事件
                // me.option.dblclickElement && me.option.dblclickElement(targetElement, e);
                if (me.option.textEditOnDblClick) {
                    me.beginInputEdit(targetElement);
                } else {
                    me.handleDblclickElement(targetElement, e);
                }
                eventStop(e);
            });
        }

        targetElement.dblclick(function (e) {
            eventStop(e);
            me.handleDblclickElement(targetElement, e);
        });

        // 连线选中问题
        if (targetElement.type == "path") {
            targetElement.hover(function () {
                let connectAttrs = me.getConnectBoundRect(targetElement);
                me.connectRect.attr(connectAttrs).show();
                me.connectRect.data("target", targetElement);
            }, function () {
                // this.attr("stroke-width", 2);
                // me.connectRect.attr("stroke-width", 0);
            });
        }
    };

    /**
     * 响应单击事件
     *
     * @param element
     * @param evt
     */
    handleClickElement(element, evt) {
        let me = this;
        let selectElement = me.selectElement;
        if (selectElement) {
            me.hideEditElements(selectElement);
        }
        me.showEditElements(me.selectElement = element);
        me.option.clickElement && me.option.clickElement(element, evt);
    };

    /**
     * 响应双击事件(不要主动调用)
     * @param element
     * @param evt
     */
    handleDblclickElement(element, evt) {
        let {dblclickElement, textEditOnDblClick} = this.option;
        if (typeof dblclickElement == "function") {
            dblclickElement(element, evt);
        }
        if (textEditOnDblClick) {
            this.beginInputEdit(element);
        }
        // if (this.option.enablePropertyPop) {
        //     this.openElementPropertyPop(element);
        // }
    };

    bindMouseOverOutEvent(targetElement) {
        let type = targetElement.type;
        let me = this;
        if (type == "rect" || type == "image" || type == "html") {
            let nodeType = targetElement.data("nodeType");
            if (nodeType != "Start") {
                targetElement.mouseover(function () {
                    me.dropNode = null;
                    if (!me.dragingLine) {
                        return;
                    }
                    me.showDropRect(targetElement);
                    me.dropNode = this;
                }).mouseout(function () {
                    me.hideDropRect();
                    this.attr("cursor", "default");
                    me.dropNode = null;
                });
            }
        }
    };

    showDropRect(targetElement) {
        let {dropNw, dropNe, dropSw, dropSe} = this;
        let {x, y, width, height} = targetElement.attrs;
        // 创建一个矩形path和8个矩形点
        let hiddenPathStartX = x - 5;
        let hiddenPathStartY = y - 5;
        let hiddenPathEndX = x + width + 5;
        let hiddenPathEndY = y + height + 5;

        dropNw.attr("d", "M" + (hiddenPathStartX + 5) + "," + hiddenPathStartY + "H" + hiddenPathStartX + "V" + (hiddenPathStartY + 5)).show();
        dropNe.attr("d", "M" + (hiddenPathEndX - 5) + "," + hiddenPathStartY + "H" + hiddenPathEndX + "V" + (hiddenPathStartY + 5)).show();
        dropSw.attr("d", "M" + (hiddenPathStartX + 5) + "," + hiddenPathEndY + "H" + hiddenPathStartX + "V" + (hiddenPathEndY - 5)).show();
        dropSe.attr("d", "M" + (hiddenPathEndX - 5) + "," + hiddenPathEndY + "H" + hiddenPathEndX + "V" + (hiddenPathEndY - 5)).show();
    };

    hideDropRect() {
        let {dropNw, dropNe, dropSw, dropSe} = this;
        dropNw.hide();
        dropNe.hide();
        dropSw.hide();
        dropSe.hide();
    };

    // beginCreateLink(targetElement) {
    //     if (this.selectElement && this.selectElement != targetElement) {
    //         this.hideEditElements(this.selectElement);
    //     }
    //     this.showEditElements(this.selectElement = targetElement);
    //     // 设置激活状态
    //     this.activeFromElement = targetElement;
    //     this.alertMessage("点击节点完成连线,空白区域取消！", 3);
    // };
    //
    // createLink(activeFromElement, targetElement) {
    //
    //     if (activeFromElement == targetElement) {
    //         this.alertMessage("不能自连接",  3);
    //         return;
    //     }
    //     // 校验
    //     let sameContainer = activeFromElement.data("container") == targetElement.data("container");
    //     if (!sameContainer) {
    //         this.alertMessage("不在同一个容器不能相连", 3);
    //         return;
    //     }
    //     let fromDateType = activeFromElement.data("type");
    //     let toDateType = targetElement.data("type");
    //     // 开始节点只能单出
    //     if (toDateType == "start") {
    //         this.alertMessage("开始节点不能作为目的节点", 3);
    //         return;
    //     }
    //     // 判断from和to2个点之间是否已经存在了连线
    //     // 遍历from的out即可
    //     let outLines = activeFromElement.data("out");
    //     if (outLines) {
    //         for (var i in outLines) {
    //             let outLine = outLines[i];
    //             let tempToElement = outLine.data("to");
    //             if (tempToElement == targetElement) {
    //                 // 已存在
    //                 this.alertMessage("连线已存在", 3);
    //                 return;
    //             }
    //         }
    //     }
    //     console.log("createLink ");
    //     // let standardMode = this.designMode != "Simple";
    //     // // 标准模式校验分支及单进单出
    //     // if (standardMode) {
    //     //     // 除去分支节点外，其他节点只能单出
    //     //     if (fromDateType != "diverage") {
    //     //         let outLines = activeFromElement.data("out");
    //     //         let len = 0 || (outLines && Object.getOwnPropertyNames(outLines).length);
    //     //         if (len > 0) {
    //     //             this.alertMessage("非分支节点只能单出", 3);
    //     //             return;
    //     //         }
    //     //     }
    //     //     if (toDateType != "converge") {
    //     //         let inLines = targetElement.data("in");
    //     //         let len = 0 || (inLines && Object.getOwnPropertyNames(inLines).length);
    //     //         if (len > 0) {
    //     //             this.alertMessage("非聚合节点只能单进", "red");
    //     //             return;
    //     //         }
    //     //     }
    //     // }
    //     this.createPath(activeFromElement, targetElement);
    //     return true;
    // };

    /**
     * 通过translate平移
     *
     * @param x
     * @param y
     */
    translateTo(x, y) {
        this.translateX = x;
        this.translateY = y;
        this.updateWrapperTransform();
        // if(this.shiftMode) {
        //     this.paper.node.childNodes.forEach(child => {
        //         assign(child.style, {
        //             transform: `translate(${x}px, ${y}px)`
        //         });
        //     });
        //
        //     // update HtmlNodes
        //     for (let elementId in this.elements) {
        //         let element = this.elements[elementId];
        //         if (element.type == "html") {
        //             assign(element.node.style, {
        //                 transform: `translate(${x}px, ${y}px)`
        //             });
        //         }
        //     }
        // } else {
        //
        // }
    };

    updateWrapperTransform() {
        let x = this.translateX;
        let y = this.translateY;
        let scaleValue = this.scaleValue || 1;
        // flow wrapper
        assign(this.flowWrapper.style, {
            transform: `translate(${x}px, ${y}px) scale(${scaleValue})`,
            // minWidth: `${newWidth}px`,
            // minHeight: `${newHeight}px`,
        });
        console.log("updateWrapperTransform x, y", x, y);

        // let newWidth = width, newHeight = height;
        // // 计算offset
        // let ox = 0, oy = 0;
        // if (scaleValue < 1) {
        //     newWidth = width / scaleValue;
        //     newHeight = height / scaleValue;
        //     ox = -newWidth * (1 - scaleValue) / 2;
        //     oy = -newHeight * (1 - scaleValue) / 2;
        // }

        // // offset
        // assign(wrapperParent.style, {
        //     transform: `translate(${ox}px, ${oy}px)`
        // });

        // after set transform
        let wrapperParent = this.flowWrapper.parentNode;
        let {left: l1, top: t1} = wrapperParent.getBoundingClientRect();
        let {left: l2, top: t2} = this.flowWrapper.getBoundingClientRect();
        this.offsetX = l2 - l1;
        this.offsetY = t2 - t1;
    };

    /**
     * 指定元素平移
     *
     * @param elements
     * @param dx
     * @param dy
     */
    elementsPanTo(elements, dx, dy) {
        for (let elementId in elements) {
            let element = elements[elementId];
            this.moveTo(element, dx, dy);
        }

        // update
        for (let elementId in elements) {
            let element = elements[elementId];
            if (element.type == "path") {
                this.updateLine(element);
            } else {
                // this.updateElements(element);
            }
        }
    };

    /***
     * 整体平移
     *
     * @param dx x方向偏移量
     * @param dy y方向偏移量
     * @param actionFlag
     */
    panTo(dx, dy, actionFlag) {
        this.hideEditElements(null);
        if (this.enableHistory() && actionFlag) {
            let me = this;
            this.addAction({
                undo() {
                    me.panTo(-dx, -dy, false);
                },
                redo() {
                    me.panTo(dx, dy, false);
                }
            });
        }
        this.translateTo(0, 0);
        this.elementsPanTo(this.elements, dx, dy);
        this.moveTo(this.groupSelection, dx, dy);
    };

    /** move */
    moveTo(element, dx, dy) {
        if (!element || !element.type) return;
        let type = element.type;
        if (type == "path") {
            let startElement = element && element.data("start");
            if (startElement) {
                this.moveTo(startElement, dx, dy);
                let nextElement = startElement.data("right");
                while (nextElement) {
                    this.moveTo(nextElement, dx, dy);
                    let temp = nextElement;
                    let leftRect = temp.data("leftRect");
                    this.moveTo(leftRect, dx, dy);
                    nextElement = nextElement.data("right");
                }
            }
        } else {
            let {x, y} = element.attrs;
            element.attr({x: x + dx, y: y + dy});
        }

        // move text
        let textElement = element.data("text");
        if (textElement) {
            let {x, y} = textElement.attrs;
            textElement.attr({x: x + dx, y: y + dy});
        }

        // move icon
        let icon = element.data("icon");
        if (icon) {
            let {x, y} = icon.attrs;
            icon.attr({x: x + dx, y: y + dy});
        }
    };

    /**
     * 设置是否可以平移
     *
     * @param panable
     */
    setPanable(panable) {
        this.option.panable = !!panable;
    };

    getContextMenuFn(targetElement) {
        // let elementType = targetElement.type;
        // let dataType = targetElement.data("type");
        //
        // let me = this;
        // let contextMenuFn = function (id) {
        //     let contextData = [{
        //         text: "删除",
        //         iconCls: 'icon-stop',
        //         click: function () {
        //             me.removeElement(targetElement);
        //         }
        //     }];
        //
        //     if (elementType == "rect" || elementType == "image") {
        //
        //         if (dataType != "end") {
        //             contextData.push("-");
        //             contextData.push({
        //                 text: "连接到",
        //                 click: function () {
        //                     me.beginCreateLink(targetElement);
        //                 }
        //             });
        //         }
        //
        //         let inLines = targetElement.data("in");
        //         let horizontalItems = [];
        //         if (inLines != null) {
        //             let inLinkIds = Object.getOwnPropertyNames(inLines);
        //             if (inLinkIds.length > 0) {
        //                 for (let i = 0; i < inLinkIds.length; i++) {
        //                     let lineElement = inLines[inLinkIds[i]];
        //                     let fromElement = lineElement.data("from");
        //                     horizontalItems.push({
        //                         text: fromElement.data("meta").nodeName,
        //                         target: fromElement,
        //                         click: function () {
        //                             me.horizontalAlign(targetElement, this.target);
        //                         }
        //                     });
        //                 }
        //             }
        //         }
        //         let outLines = targetElement.data("out");
        //         if (outLines != null) {
        //             let outLinkIds = Object.getOwnPropertyNames(outLines);
        //             if (outLinkIds.length > 0) {
        //                 for (let i = 0; i < outLinkIds.length; i++) {
        //                     let lineElement = outLines[outLinkIds[i]];
        //                     let toElement = lineElement.data("to");
        //                     horizontalItems.push({
        //                         text: toElement.data("meta").nodeName,
        //                         target: toElement,
        //                         click: function () {
        //                             me.horizontalAlign(targetElement, this.target);
        //                         }
        //                     });
        //                 }
        //             }
        //         }
        //         if (horizontalItems.length > 0) {
        //             contextData.push("-");
        //             if (horizontalItems.length == 1) {
        //                 contextData.push({
        //                     text: "水平对齐",
        //                     target: horizontalItems[0].target,
        //                     click: function () {
        //                         me.horizontalAlign(targetElement, this.target);
        //                     }
        //                 }, {
        //                     text: "垂直对齐",
        //                     target: horizontalItems[0].target,
        //                     click: function () {
        //                         me.verticalAlign(targetElement, this.target);
        //                     }
        //                 });
        //             } else {
        //                 let verticalItems = [];
        //                 $(horizontalItems).each(function () {
        //                     let horizontalItem = this;
        //                     verticalItems.push({
        //                         text: horizontalItem.text,
        //                         target: horizontalItem.target,
        //                         click: function () {
        //                             me.verticalAlign(targetElement, this.target);
        //                         }
        //                     });
        //                 });
        //
        //
        //
        //
        //
        //
        //
        //                 contextData.push({
        //                     text: "水平对齐",
        //                     items: horizontalItems
        //                 }, {
        //                     text: "垂直对齐",
        //                     items: verticalItems
        //                 });
        //             }
        //         }
        //         return contextData;
        //     } else if (elementType == "path") {
        //         return contextData;
        //     }
        // };
        // return contextMenuFn;
    };

    /**
     * 创建开始节点
     *
     * @param x
     * @param y
     * @param r
     * @returns {*}
     */
    createStartNode(x, y) {
        // return this.createImage(imgs.start, x || 100, y || 150, 48, 48, "Start");
        return this.createHTMLNode("start", x || 100, y || 150, 48, 48, "Start").attr({
            color: this.themeColor
        });
    };

    /**
     * 创建结束节点(使用svg)
     */
    createEndNode(x, y) {
        // return this.createImage(imgs.end, x || 850, y || 150, 48, 48, "End", true);
        return this.createHTMLNode("end", x || 100, y || 150, 48, 48, "End").attr({
            color: this.themeColor
        });
    };

    /**
     * 创建分支节点(默认类型为xor)
     *
     * @param x
     * @param y
     * @returns {*}
     */
    createSplitNode(x, y) {
        return this.createHTMLNode("xor", x || 100, y || 150, 64, 64, "Split", (node) => new HtmlSplitElementData(node)).attr({
            color: this.themeColor
        });
    };

    /**
     * 创建聚合节点
     */
    createJoinNode(x, y) {
        return this.createHTMLNode("join", x || 100, y || 150, 64, 64, "Join").attr({
            color: this.themeColor
        });
    };

    /**
     * 创建html节点(开始，结束，分支，聚合)
     *
     * @param src
     * @param x
     * @param y
     * @param w
     * @param h
     * @param nodeType
     * @returns {*}
     */
    createHTMLNode(type, x, y, w, h, nodeType, createFunction) {
        let htmlElement = this.renderHtmlNode(type, x, y, w, h, createFunction);
        this.setUUID(htmlElement);
        onNodeCreated(htmlElement, this);
        htmlElement.data("handler", {});
        htmlElement.data("nodeType", nodeType);
        this.initElement(htmlElement);
        return htmlElement;
    };

    /**
     * 创建矩形节点
     *
     * @param x
     * @param y
     * @param width
     * @param height
     * @returns {*}
     */
    createNode(x, y, width, height) {
        width = width || 100;
        height = height || 80;
        let rect = this.renderRect(x, y, width || 100, height || 80, 4);
        this.setUUID(rect);
        rect.attr({
            stroke: this.themeColor,
            "stroke-width": this.settings.nodeStrokeWith,
            fill: this.settings.nodeBackgroundColor
        });
        rect.data("handler", {});

        // create text element use html
        let text = this.renderHtmlText(0, 0, width * 0.8).attr({
            text: this.settings.nodeName + " " + this.nextId()
        });
        rect.data("text", text);

        // create icon
        let iconNode = this.renderHtmlNode("empty", x + 5, y + 5, 20, 20).attr({
            color: this.themeColor
        });
        rect.data("icon", iconNode)

        onNodeCreated(rect, this);
        this.initElement(rect);
        return rect;
    };

    handleElementAction(element) {
        if (!element) return;
        if (this.enableHistory()) {
            let me = this;
            let elementId = element.id;
            let elementData = this.toElementData(element);
            this.addAction({
                undo() {
                    me.deleteElementById(elementId);
                },
                redo() {
                    me.fromElementData(elementData);
                }
            });
        }
    };

    /**
     * 创建业务节点
     *
     * @param x
     * @param y
     * @returns {*}
     */
    createBusinessNode(x, y) {
        return this.createTypeNode(NodeTypes.Business, x, y);
    };

    /**
     * 创建服务节点
     *
     * @param x
     * @param y
     * @returns {*}
     */
    createServiceNode(x, y) {
        return this.createTypeNode(NodeTypes.Service, x, y);
    };

    /**
     * 创建手工节点
     *
     * @param x
     * @param y
     * @returns {*}
     */
    createManualNode(x, y) {
        return this.createTypeNode(NodeTypes.Manual, x, y);
    };

    /**
     * 创建消息节点
     *
     * @param x
     * @param y
     * @returns {*}
     */
    createMessageNode(x, y) {
        return this.createTypeNode(NodeTypes.Message, x, y);
    };

    createTypeNode(type, x, y) {
        let rect = this.createNode(x, y);
        rect.data("nodeType", type);
        let iconNode = this.renderHtmlNode(type.toLowerCase(), x + 5, y + 5, 20, 20).attr({
            color: this.themeColor
        });
        rect.data("icon", iconNode)
        return rect;
    };

    /**
     * 根据数据构建节点
     *
     * @param node
     * @returns {null}
     */
    createNodeElement(node) {
        let {id, type, component} = node;
        let {type: componentType, attrs, textAttrs} = component;
        let nodeElement = null;
        if (componentType == "rect") {
            nodeElement = this.renderRect(0, 0, 0, 0, 0);
        } else if (componentType == "image") {
            //
            // element = this.loadImageElement(node, editable);
            // element.data("meta", assign(node.meta, element.data("meta") || {}));
        }
        nodeElement.id = id;
        if (attrs.rx) {
            // 圆角处理
            nodeElement.attrs.r = attrs.rx;
        }
        nodeElement.data("nodeType", type);
        nodeElement.attr(attrs);

        // create text
        let nodeText = this.renderHtmlText(0, 0, attrs.width * 0.8).attr(textAttrs);
        nodeElement.data("text", nodeText);

        //if has icon create icon(svg)
        let icon = type.toLowerCase();
        let {x, y} = attrs;
        let iconNode = this.renderHtmlNode(icon, x + 5, y + 5, 20, 20).attr({
            color: this.themeColor
        });
        nodeElement.data("icon", iconNode)

        this.initElement(nodeElement);
        return nodeElement;
    };

    /**
     * 根据数据生成元素（html）
     *
     * @param id
     * @param type
     * @param component
     * @param nodeType
     * @param createFunction
     * @returns {*}
     */
    loadHTMLElement(id, type, component, nodeType, createFunction) {
        let {attrs} = component;
        let htmlElement = this.renderHtmlNode(type, 0, 0, 0, 0, createFunction);
        htmlElement.id = id;
        htmlElement.data("nodeType", nodeType);
        htmlElement.attr(attrs);
        this.initElement(htmlElement);
        return htmlElement;
    };

    /**
     * 根据数据生成元素（image）
     *
     * @param id
     * @param src
     * @param component
     * @param nodeType
     * @returns {*}
     */
    loadImageElement(id, src, component, nodeType) {
        let {attrs} = component;
        attrs.src = src;
        let image = this.renderImage("", 0, 0, 0, 0);
        image.id = id;
        image.data("nodeType", nodeType);
        image.attr(attrs);
        this.initElement(image);
        return image;
    };

    /**
     * 根据数据回显渲染连线（path）
     *
     * @param connectData
     * @param fromElement
     * @param toElement
     * @returns {*}
     */
    createConnectElement(connectData, fromElement, toElement) {
        let {id, component} = connectData;
        let {attrs, textAttrs} = component;

        let connect = this.paper.path("").attr(attrs);
        connect.id = id;
        this.setConnectArrow(connect);

        // text
        let pathText = this.renderHtmlText(0, 0, this.option.maxPathTextWidth || 100).attr(textAttrs);
        connect.data("text", pathText);

        // bind data
        connect.data("from", fromElement);
        connect.data("to", toElement);

        let outLines = fromElement.data("out") || {};
        outLines[id] = connect;
        fromElement.data("out", outLines);

        let inLines = toElement.data("in") || {};
        inLines[id] = connect;
        toElement.data("in", inLines);

        // create control elements
        let points = pathDToPoints(attrs.path || attrs.d);
        let len = points.length;
        let startElement, endElement;

        let controlElements = [];
        for (let i = 0; i < len; i++) {
            let point = points[i];
            let controlElement = this.createControlDragRect(point[1], point[2], connect);
            controlElement.data("cpIndex", -1);
            controlElements.push(controlElement);
            if (i == 0) {
                startElement = controlElement;
                startElement.data("fromNode", fromElement);
                startElement.data("type", "start");
                connect.data("start", startElement);
            } else {
                let prevControlElement = controlElements[i - 1];
                prevControlElement.data("right", controlElement);
                controlElement.data("left", prevControlElement);

                let centerDragRect = this.createControlDragRect((prevControlElement.attr("x") + controlElement.attr("x") + 5) / 2, (prevControlElement.attr("y") + controlElement.attr("y") + 5) / 2, connect);
                centerDragRect.data("cpIndex", 0);
                centerDragRect.data("left", prevControlElement);
                centerDragRect.data("right", controlElement);

                prevControlElement.data("rightRect", centerDragRect);
                controlElement.data("leftRect", centerDragRect);
            }
            if (i == len - 1) {
                endElement = controlElement;
                endElement.data("toNode", toElement);
                endElement.data("type", "end");
                connect.data("end", endElement);
            }
        }
        // hide edit elements
        this.hideEditElements(connect);
        // bind select event
        this.bindSelectEvent(connect);
        this.register(connect);
        return connect;
    };

    // create path
    createPath(fromNode, toNode) {
        let stroke = this.themeColor;
        let linkPath = this.paper.path("").attr({
            stroke,
            "stroke-width": 2
        });
        this.setConnectArrow(linkPath, stroke);
        this.setUUID(linkPath);
        let pathStyle = this.option.pathStyle || "broken";
        linkPath.data("pathStyle", pathStyle);
        linkPath.data("from", fromNode);
        linkPath.data("to", toNode);

        let outLines = fromNode.data("out") || {};
        outLines[linkPath.id] = linkPath;
        fromNode.data("out", outLines);

        let fromNodeType = fromNode.data("nodeType");
        let isFromAnd = fromNode.data("gateway") == "AND";
        let conditionTypeFlag = keys(outLines).length > 1 && !isFromAnd;
        if (!conditionTypeFlag) {
            if (fromNodeType == "Split" && !isFromAnd) {
                conditionTypeFlag = true;
            }
        }
        if (conditionTypeFlag) {
            this.setConnectType(linkPath, this.option.defaultConditionType || "Script");
        } else {
            this.setConnectType(linkPath, "Always");
        }
        onConnectCreated(linkPath, this);

        let inLines = toNode.data("in") || {};
        inLines[linkPath.id] = linkPath;
        toNode.data("in", inLines);

        // 初始化连线
        this.resetPathData(linkPath, fromNode, toNode, pathStyle);
        // 绑定事件
        this.bindSelectEvent(linkPath);
        // 隐藏编辑状态
        this.hideEditElements(null);
        // 选中
        this.selectElement = linkPath;
        // 注册连线
        this.register(linkPath);
        // 可撤销
        this.handleElementAction(linkPath);
        // return
        return linkPath;
    };

    /**
     * 重置路径连线数据
     *
     * @param pathElement
     * @param fromElement
     * @param toElement
     * @param pathStyle  连线风格
     */
    resetPathData(pathElement, fromElement, toElement, pathStyle) {
        // reset pathElement
        this.removePathRelationRects(pathElement);
        // 直线数据
        let linePathData = this.getPathData(fromElement, toElement);
        let pathStartPoint = linePathData.start;
        let pathEndPoint = linePathData.end;

        // 暂时设置至直线数据
        pathElement.attr("d", linePathData.data);
        pathElement.data("pathStyle", pathStyle);

        // centerDragRect.attr("x") - 10, centerDragRect.attr("y") - 10
        let startX = pathStartPoint.x, startY = pathStartPoint.y;
        let endX = pathEndPoint.x, endY = pathEndPoint.y;
        let centerX = (startX + endX) / 2, centerY = (startY + endY) / 2;

        switch (pathStyle) {
            case "broken": {
                // 创建3个控制点
                let startElement = this.createControlDragRect(startX, startY, pathElement);
                let endElement = this.createControlDragRect(endX, endY, pathElement);
                startElement.data("fromNode", fromElement);
                endElement.data("toNode", toElement);
                pathElement.data("start", startElement);
                pathElement.data("end", endElement);

                startElement.data("right", endElement);
                endElement.data("left", startElement);

                startElement.data("type", "start");
                endElement.data("type", "end");

                // 创建中间拖动的矩形
                let centerDragRect = this.createControlDragRect(centerX, centerY, pathElement);
                centerDragRect.data("cpIndex", 0);
                centerDragRect.data("left", startElement);
                centerDragRect.data("right", endElement);

                startElement.data("rightRect", centerDragRect);
                endElement.data("leftRect", centerDragRect);
                // 线段
                break;
            }
            // case "straight": {
            //     // 直线 donothing
            //     break;
            // }
            // case "h2v": {
            //     break;
            // }
            // case "v2h": {
            //     break;
            // }
            // case "curve": {
            //     break;
            // }
            default: {
                break;
            }
        }

        // 文本
        let pathText = pathElement.data("text");
        if (!pathText) {
            pathText = this.renderHtmlText(centerX - 2.5 - 10, centerY - 2.5 - 10, this.option.maxPathTextWidth || 100).attr({
                text: this.settings.linkName || " "
            });
            pathElement.data("text", pathText);
        } else {
            pathText.attr({x: centerX - 2.5 - 10, y: centerY - 2.5 - 10});
        }
    };

    /**
     * 获取连线的矩形信息（x,y,w,h）
     *
     * @param connect
     */
    getConnectBoundRect(connect) {
        let type = connect.type;
        if (type != "path") {
            return null;
        }
        let {x, y, width, height} = connect.getBBox();
        return {
            x: x - 5,
            y: y - 5,
            width: width + 10,
            height: height + 10
        }

        // let pathDatas = connect.attr("d");
        //
        // let minX = undefined, minY = undefined, maxX = undefined, maxY = undefined;
        // let lastX = 0, lastY = 0;
        // for (let pathData of pathDatas) {
        //     let code = pathData[0];
        //     let x, y;
        //     let breakFlag = false;
        //     switch (code) {
        //         case "M":
        //         case "L": {
        //             x = pathData[1];
        //             y = pathData[2];
        //             break;
        //         }
        //         case "H": {
        //             x = pathData[1];
        //             y = lastY;
        //             break;
        //         }
        //         case "V": {
        //             x = lastX;
        //             y = pathData[1];
        //             break;
        //         }
        //         case "h": {
        //             x = lastX + pathData[1];
        //             y = lastY;
        //             break;
        //         }
        //         case "v": {
        //             x = lastX;
        //             y = lastY + pathData[1];
        //             break;
        //         }
        //         default: {
        //             breakFlag = true;
        //         }
        //     }
        //
        //     if (breakFlag) {
        //         break;
        //     }
        //     if (minX === undefined) {
        //         minX = maxX = x;
        //         minY = maxY = y;
        //     } else {
        //         minX = Math.min(minX, x);
        //         minY = Math.min(minY, y);
        //
        //         maxX = Math.max(maxX, x);
        //         maxY = Math.max(maxY, y);
        //     }
        //     lastX = x;
        //     lastY = y;
        // }
        //
        // return {
        //     x: minX - 5,
        //     y: minY - 5,
        //     width: maxX - minX + 10,
        //     height: maxY - minY + 10
        // }
    };

    createControlDragRect(x, y, pathElement) {
        let controlDragRect = this.renderRect(x - 2.5, y - 2.5, 5,
            5, 2.5, 2.5).attr({
            fill: "#fff",
            stroke: this.themeColor,
            cursor: 'move'
        });
        controlDragRect.data("type", "center");
        if (pathElement) {
            controlDragRect.data("host", pathElement);
        }
        let me = this;
        // 绑定事件
        controlDragRect.drag(function (dx, dy, x, y) {
            me.controlOnMove(this, dx, dy, x, y);
        }, function () {
            me.controlOnStart(this);
            me.dragingElement = this;
        }, function () {
            me.controlOnUp(this);
            me.dragingElement = null;
        });
        return controlDragRect;
    };

    controlOnMove(controlRect, dx, dy, x, y) {

        let scaleValue = this.scaleValue || 1;
        dx /= scaleValue;
        dy /= scaleValue;

        controlRect.attr({
            x: controlRect.ox + dx,
            y: controlRect.oy + dy
        });
        let type = controlRect.data("type");
        let host = controlRect.data("host");

        // if (host.data("container")) {
        //     // 更新相对位置
        //     this.relativePosition(controlRect, host.data("container"));
        // }

        if (type == "start") {
            // 解决zindex导致move事件不响应问题
            controlRect.hide();
            let rightElement = controlRect.data("right");
            let rightDropRect = controlRect.data("rightRect");
            rightDropRect.attr({
                x: (controlRect.attr("x") + rightElement.attr("x")) / 2,
                y: (controlRect.attr("y") + rightElement.attr("y")) / 2
            });
            // update path
            this.updatePath(host);
        } else if (type == "end") {
            controlRect.hide();
            let leftElement = controlRect.data("left");
            let leftDropRect = controlRect.data("leftRect");
            leftDropRect.attr({
                x: (controlRect.attr("x") + leftElement.attr("x")) / 2,
                y: (controlRect.attr("y") + leftElement.attr("y")) / 2
            });
            // update path
            this.updatePath(host);
        } else {
            this.updatePathByControlRect(controlRect);
        }
        this.validateDropLink(host, type == "start");
    };

    controlOnStart(controlRect) {
        controlRect.ox = controlRect.attr("x");
        controlRect.oy = controlRect.attr("y");
        controlRect.data("dragging", true);

        let hostElement = controlRect.data("host");
        let type = controlRect.data("type");
        if (type == "start" || type == "end") {
            this.dragingLine = hostElement;
        }
    };

    controlOnUp(controlRect) {
        // 当鼠标落地后还原设置的相关控制属性
        controlRect.data("disableRestore", null);
        if (controlRect.data("restore")) {
            controlRect.data("restore", null);
            controlRect.data("cpIndex", 0);
        }

        let type = controlRect.data("type");
        let hostElement = controlRect.data("host");
        // 判断是否含可接收的节点
        let dropNode = this.dropNode;
        if (dropNode) {

            if (!this.validateDropLink(hostElement, type == "start")) {
                // 还原
                this.updatePathBound(hostElement);
                this.updatePath(hostElement);
                this.dragingLine = null;
                return;
            }

            let selectPath = hostElement;
            if (type == "start") {
                controlRect.show();
                /* 清除当前连线原from节点的内存关系*/
                let oldFromNode = controlRect.data("fromNode");
                if (oldFromNode.data("out")) {
                    delete oldFromNode.data("out")[selectPath.id];
                }
                /* 设置当前拖动连线新from节点并建立关系*/
                let newFromNode = dropNode;
                selectPath.data("from", newFromNode);
                controlRect.data("fromNode", newFromNode);

                let outLines = newFromNode.data("out") || {};
                outLines[selectPath.id] = selectPath;
                newFromNode.data("out", outLines);

                this.updatePathBound(selectPath);
                this.updatePath(selectPath);

            } else if (type == "end") {
                controlRect.show();
                /* 清除当前连线原目的节点的内存关系*/
                let oldToNode = selectPath.data("to");
                if (oldToNode.data("in")) {
                    delete oldToNode.data("in")[selectPath.id];
                }
                let newToNode = dropNode;
                /* 设置当前拖动连线新的目的节点并建立关系*/
                selectPath.data("to", newToNode);
                controlRect.data("toNode", newToNode);

                let inLines = newToNode.data("in") || {};
                inLines[selectPath.id] = selectPath;
                newToNode.data("in", inLines);

                this.updatePathBound(selectPath);
                this.updatePath(selectPath);
            }
        } else {
            // 还原
            this.updatePathBound(hostElement);
            this.updatePath(hostElement);
        }

        this.dragingLine = null;
    };

    /**
     * 设置元素可以拖动
     *
     * @param target
     */
    dragableElement(target) {
        let me = this;
        let context = {
            moved: false
        };
        // 支持拖拽
        target.drag((dx, dy) => {
            // drag move
            if (!me.option.editable) return;
            if (!context.moved) {
                if (dx * dx + dy * dy > 0) {
                    context.moved = true;
                } else {
                    return;
                }
            }
            me.elementDragMove(target, dx, dy);
        }, () => {
            // drag start
            if (!me.option.editable) return;
            me.elementDragStart(target);
            me.dragingElement = target;
            // history
            if (me.enableHistory()) {
                context.undoData = JSON.stringify(me.getData());
            }
            return false;
        }, () => {
            // drag up
            if (!me.option.editable) return;
            me.dragingElement = null;
            target.attr({opacity: 1});
            me.hideAlignLines();
            if (me.enableHistory() && context.moved) {
                let data = JSON.stringify(me.getData());
                let undoData = context.undoData;
                me.addAction({undo: () => me.setData(undoData), redo: () => me.setData(data)});
            }
            context = {};
        });
    };

    /**
     * 获取可视的节点数量
     */
    getVisibleNodes() {
        let elements = this.elements;
        let nodes = [];
        for (let element of values(elements)) {
            if (element.type == "path") continue;
            nodes.push(element);
        }
        return nodes;
    };

    /**
     * align lines
     */
    handleAlignLines(target) {
        if (this.option.disableAlignLine || !target) {
            return;
        }
        try {
            let {x: x0, y: y0, width: w0, height: h0} = target.attrs;
            let c0 = {
                x: x0 + w0 / 2,
                y: y0 + h0 / 2
            }
            // 检查所有的
            let nodeElements = this.getVisibleNodes();

            // 声明显示标志
            let horizontalVisible = false, verticalVisible = false;
            let cx = 0, cy = 0;
            for (let nodeElement of nodeElements) {
                if (nodeElement == target) continue;
                let {x: x1, y: y1, width: w1, height: h1} = nodeElement.attrs;
                let c1 = {
                    x: x1 + w1 / 2,
                    y: y1 + h1 / 2
                }
                let dx = c0.x - c1.x, dy = c0.y - c1.y;
                if (!verticalVisible && dx * dx < 25) {
                    verticalVisible = true;
                    cx = c1.x;
                }
                if (!horizontalVisible && dy * dy < 25) {
                    horizontalVisible = true;
                    cy = c1.y;
                }
            }

            if (horizontalVisible) {
                this.horizontalLine.attr({
                    width: "100%",
                    y: cy
                }).show();
                // 矫正target位置
                target.attr({
                    y: cy - h0 / 2
                });
            } else {
                this.horizontalLine.hide();
            }

            if (verticalVisible) {
                this.verticalLine.attr({
                    x: cx,
                    height: "100%"
                }).show();
                // 矫正target位置
                target.attr({
                    x: cx - w0 / 2
                });
            } else {
                this.verticalLine.hide();
            }
        } catch (err) {
            console.error(err);
        }
    };

    // 隐藏lines
    hideAlignLines() {
        if (this.option.disableAlignLine) {
            return;
        }
        this.horizontalLine.hide();
        this.verticalLine.hide();
    };

    initElement(target) {
        this.dragableElement(target);
        this.updateElements(target, true);
        this.hideEditElements(target);
        // 绑定事件
        this.bindSelectEvent(target);
        // 绑定鼠标over和out事件
        this.bindMouseOverOutEvent(target);
        // 注册
        this.register(target);
    };

    elementDragMove(element, dx, dy) {
        let scaleValue = this.scaleValue;
        let location = {
            x: element.ox + dx / scaleValue,
            y: element.oy + dy / scaleValue
        };

        // set position
        element.attr(location);

        // 处理对齐线的显示和隐藏
        this.handleAlignLines(element);

        this.updateElements(element);
        this.showEditElements(element);
    };

    elementDragStart(element) {
        // storing original coordinates
        element.ox = element.attr("x");
        element.oy = element.attr("y");
        element.attr({
            opacity: .8
        });
        this.endInputEdit();
        element.attr("cursor", "move");
    };

    resizeOnStart(rect) {
        // storing original coordinates
        rect.ox = rect.attr("x");
        rect.oy = rect.attr("y");
    };

    resizeOnMove(rect, dx, dy, x, y) {
        let scaleValue = this.scaleValue || 1;
        rect.attr({
            x: rect.ox + dx / scaleValue,
            y: rect.oy + dy / scaleValue
        });
        let newx, newy, width, height;
        let dgl = rect.data("dgl");
        let selectRect = rect.data("host");
        if (dgl) {

            newx = min(rect.attr("x"), dgl.attr("x")) + 7.5;
            newy = min(rect.attr("y"), dgl.attr("y")) + 7.5;
            width = abs(rect.attr("x") - dgl.attr("x")) - 10;
            height = abs(rect.attr("y") - dgl.attr("y")) - 10;

            // // 子流程设置最小宽高 300 150
            // if (selectRect.data("type") == "mutiSubProcess") {
            //     // 获取容器边界信息
            //     let boundary = this.getContainerBoundary(selectRect.id);
            //     if (boundary) {
            //         width = max(width, (boundary.boundaryX - newx));
            //         height = max(height, (boundary.boundaryY - newy));
            //     }
            // } else {
            // }
            // 其他默认最小 80 30
            width = max(width, 80);
            height = max(height, 30);

            var dtn = rect.data("dtn");
            if (dtn == "w" || dtn == "e") {
                // 水平移动
                selectRect.attr({
                    x: newx,
                    width
                });
            } else if (dtn == "n" || dtn == "s") {
                // 垂 直移动
                selectRect.attr({
                    y: newy,
                    height
                });
            } else {
                // 对角移动
                selectRect.attr({
                    x: newx,
                    y: newy,
                    width,
                    height
                });
            }
        }
        this.updateElements(selectRect);
        this.showEditElements(selectRect);
    };

    linkToolOnDragMove(linkTool, dx, dy) {
        // 初始化位置
        let {x, y} = linkTool.attrs;
        let scaleValue = this.scaleValue || 1;
        // move 修改鼠标
        let mx = x + dx / scaleValue;
        let my = y + dy / scaleValue;
        // 创建一个透明的点
        let dropEndRect = linkTool.data("dropEndRect");
        let element = linkTool.data("from");
        if (dropEndRect == null) {
            dropEndRect = this.renderRect(mx, my, 20, 5)
                .attr({
                    fill: "#000000",
                    cursor: 'move',
                    opacity: 0
                }).hide();
            linkTool.data("dropEndRect", dropEndRect);
        } else {
            dropEndRect.attr({
                x: mx,
                y: my
            });
        }
        let virtualData = this.getPathData(element, dropEndRect);
        let virtualPath = linkTool.data("virtualPath");
        if (virtualPath == null) {
            virtualPath = this.paper.path(virtualData.data).attr({
                stroke: this.themeColor,
                "stroke-width": 2,
                fill: this.themeColor,
                "stroke-dasharray": "2 2"
            });
            linkTool.data("virtualPath", virtualPath);
        } else {
            virtualPath.attr("d", virtualData.data);
        }
        virtualPath.show();

        virtualPath.data("from", element);
        this.dragingLine = virtualPath;

        // 设置禁用样式
        this.validateDropLink(virtualPath);
    };

    linkToolOnDragUp(linkTool) {
        this.hideDropRect();
        let virtualPath = linkTool.data("virtualPath");
        if (virtualPath) {
            virtualPath.hide();
        } else {
            // alert(" error catch !");
        }
        let dropNode = this.dropNode;
        if (dropNode) {

            if (!this.validateDropLink(virtualPath)) {
                console.log("vk false");
                this.dragingLine = null;
                return;
            }
            let fromNode = virtualPath.data("from");
            if (fromNode == dropNode) {
                // 如果to环节和当前from环节相同直接返回
                this.dragingLine = null;
                return;
            }
            // 创建link之前判断是否from和to是否连通（不一定是直接相连）
            let isConnected = this.isConnected(fromNode, dropNode, true);
            let outPath = this.createPath(fromNode, dropNode);
            this.dropNode = null;
            this.dragingLine = null;

            // 如果from和to已经连通，有可能是回退或新增的分支连线，处理用户体验方面的问题（连线被遮住）
            if (isConnected) {
                // 移动中央控制点，终点位置取fromNode和dropNode连线线段的2点做中垂线，偏离2点的15度角度
                let x1 = outPath.data("start").attr("x");
                let y1 = outPath.data("start").attr("y");

                let x2 = outPath.data("end").attr("x");
                let y2 = outPath.data("end").attr("y");

                let x0 = 0, y0 = 0, len, PI = 3.141592653;
                if (y1 == y2) {
                    // 同一水平线上
                    x0 = (x1 + x2) / 2;
                    len = abs(x2 - x1);
                    y0 = y1 - len / 2 * tan(15 / 180 * PI);
                    y0 = max(y0, 1);
                } else if (x1 == x2) {
                    // 暂时不处理
                } else {
                    len = sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
                    let h = len / 2 * tan(15 / 180 * PI);

                    let centerX = (x1 + x2) / 2;
                    let centerY = (y1 + y2) / 2;

                    // 中垂线斜率 = -1/k',k' = (y2 - y1 / x2 - x1)
                    let k = -(x2 - x1) / (y2 - y1);
                    let angle = atan(k);
                    let sinValue = sin(angle);
                    let cosValue = cos(angle);

                    let dy = h * sinValue;
                    let dx = h * cosValue;

                    x0 = dx + centerX;
                    y0 = dy + centerY;
                }

                if (x0 || y0) {
                    let controlElement = outPath.data("start").data("rightRect");
                    controlElement.attr({
                        x: x0,
                        y: y0
                    });
                    // if (outPath.data("container")) {
                    //     this.relativePosition(controlElement, outPath.data("container"));
                    // }
                    this.updatePathByControlRect(controlElement);
                }
            }
        } else {
            this.dragingLine = null;
        }
    };

    validateDropLink(pathElement, reverse) {

        // let standardMode = false; // this.designMode != "Simple";
        let dropNode = this.dropNode;
        if (dropNode) {

            // let isSameContainer = function (fromElement, toElement) {
            //     let sameContainer = fromElement.data("container") == toElement.data("container");
            //     if (!sameContainer) {
            //         dropNode.attr("cursor", "not-allowed");
            //     }
            //     return sameContainer;
            // };

            // 除去分支节点外，其他节点只能单出
            let from = pathElement.data("from");
            let to = pathElement.data("to");

            // let fromDataType = from.data("type");
            // let dateType = dropNode.data("type");
            // // 开始节点只能单出
            // if (dateType == "start" && !reverse) {
            //     dropNode.attr("cursor", "not-allowed");
            //     return false;
            // }

            // // 如果2个容器不一致，直接禁用
            // let sameContainer = reverse ? isSameContainer(dropNode, to) : isSameContainer(from, dropNode);
            // if (!sameContainer) {
            //     return false;
            // }

            // 判断from和to2个点之间是否已经存在了连线
            // 遍历from的out即可
            let outLines = reverse ? dropNode.data("out") : from.data("out");
            if (outLines) {
                for (let i in outLines) {
                    let outLine = outLines[i];
                    let tempToElement = outLine.data("to");
                    if (tempToElement == (reverse ? to : dropNode)) {
                        // 已存在
                        dropNode.attr("cursor", "not-allowed");
                        return false;
                    }
                }
            }

            return true;
            // // 如果reverse为true说明是反向连接，dropNode即将是pathElement的from端
            // if (reverse) {
            //     // 判断pathElement是否本来就是dropNode的from（连线的开始点拉出来后又还原回去），如果是返回true
            //     if (from == dropNode) {
            //         // 还原
            //         return true;
            //     }
            //     // // 除去分支节点外，其他节点只能单出
            //     // if (dateType != "diverage") {
            //     //     let outLines = dropNode.data("out");
            //     //     let len = 0 || (outLines && Object.getOwnPropertyNames(outLines).length);
            //     //     if (len > 0) {
            //     //         dropNode.attr("cursor", "not-allowed");
            //     //         return false;
            //     //     }
            //     // }
            //     // // end节点不能作为from端
            //     // if (dateType == "end") {
            //     //     dropNode.attr("cursor", "not-allowed");
            //     //     return false;
            //     // }
            //     from = dropNode;
            // } else {
            //     // 除去聚合节点外，其他节点只能单进
            //     if (dateType != "converge") {
            //         if (to == dropNode) {
            //             // 还原
            //             return true;
            //         }
            //         let inLines = dropNode.data("in");
            //         let len = 0 || (inLines && Object.getOwnPropertyNames(inLines).length);
            //         if (len > 0) {
            //             dropNode.attr("cursor", "not-allowed");
            //             return false;
            //         }
            //     }
            //
            //     if (fromDataType != "diverage") {
            //         // if (to) {
            //         //     // 如果to存在说明是已经存在的link,当前操作可能是变更连接to端，而非virthPath
            //         //     return isSameContainer(from, dropNode);
            //         // }
            //         let outLines = from.data("out");
            //         let len = 0 || (outLines && Object.getOwnPropertyNames(outLines).length);
            //         if (len > 0) {
            //             dropNode.attr("cursor", "not-allowed");
            //             return false;
            //         }
            //     }
            //
            // }
        }
        return true;
    };

    hideEditElements(targetElement) {
        let type = null;
        if (!targetElement || (type = targetElement.type) == "rect" || type == "image" || type == "html") {
            let {
                nw, w, sw, n, s, ne, e, se, dashOuterPath,
                exchange,
                linkTool,
                nextTaskTool,
                nextSplitTool,
                nextEndTool,
                deleteTool
            } = this;
            nw.hide();
            w.hide();
            sw.hide();
            n.hide();
            s.hide();
            ne.hide();
            e.hide();
            se.hide();
            dashOuterPath.hide();
            exchange.hide();
            linkTool.hide();
            nextTaskTool.hide();
            nextSplitTool.hide();
            nextEndTool.hide();
            deleteTool.hide();
        } else if (type == "path") {
            // 连线
            let startElement = targetElement.data("start");
            startElement.hide();
            let nextElement = startElement.data("right");
            while (nextElement) {
                nextElement.hide();
                let leftRect = nextElement.data("leftRect");
                leftRect.hide();
                nextElement = nextElement.data("right");
            }
            this.deleteTool.hide();
        }

        if (targetElement) {
            targetElement.attr("cursor", "auto");
        }

        this.hidePopupMenu();
    };

    hidePopupMenu(delay) {
        let popupMenu;
        if (popupMenu = this.popupMenu) {
            setTimeout(() => {
                popupMenu.hide();
            }, delay || 200)
        }
    }

    setSelectElement(element) {
        this.selectElement = element;
        this.showEditElements(element);
    };

    showEditElements(targetElement) {
        if (!this.option.editable) {
            return;
        }
        let type = targetElement.type;
        let isImage = type == "image";
        if (type == "rect" || isImage || type == "html") {
            let {x, y, width, height} = targetElement.attrs;
            let nodeType = targetElement.data("nodeType");
            let {
                nw, w, sw, n, s, ne, e, se, dashOuterPath,
                exchange,
                linkTool,
                nextTaskTool,
                nextSplitTool,
                nextEndTool,
                deleteTool
            } = this;
            // 更新位置，并显示
            let hiddenPathStartX = x - 5;
            let hiddenPathStartY = y - 5;
            let hiddenPathEndX = x + width + 5;
            let hiddenPathEndY = y + height + 5;
            let hiddenPathWidth = hiddenPathEndX - hiddenPathStartX;
            let hiddenPathHeight = hiddenPathEndY - hiddenPathStartY;

            let outerPathD = "M" + hiddenPathStartX + "," + hiddenPathStartY + "L"
                + hiddenPathStartX + "," + hiddenPathEndY + "L"
                + hiddenPathEndX + "," + hiddenPathEndY + "L"
                + hiddenPathEndX + "," + hiddenPathStartY + "L"
                + hiddenPathStartX + "," + hiddenPathStartY;

            dashOuterPath.attr({
                path: outerPathD,
                fill: "none",
                stroke: "#909399"
            }).show();

            if (!isImage) {
                // 8个矩形点
                nw.data("host", targetElement).attr({x: hiddenPathStartX - 2.5, y: hiddenPathStartY - 2.5}).show();
                w.data("host", targetElement).attr({
                    x: hiddenPathStartX - 2.5,
                    y: hiddenPathStartY + hiddenPathHeight / 2 - 2.5
                }).show();
                sw.data("host", targetElement).attr({x: hiddenPathStartX - 2.5, y: hiddenPathEndY - 2.5}).show();
                n.data("host", targetElement).attr({
                    x: hiddenPathStartX + hiddenPathWidth / 2 - 2.5,
                    y: hiddenPathStartY - 2.5
                }).show();
                s.data("host", targetElement).attr({
                    x: hiddenPathStartX + hiddenPathWidth / 2 - 2.5,
                    y: hiddenPathEndY - 2.5
                }).show();
                ne.data("host", targetElement).attr({x: hiddenPathEndX - 2.5, y: hiddenPathStartY - 2.5}).show();
                e.data("host", targetElement).attr({
                    x: hiddenPathEndX - 2.5,
                    y: hiddenPathStartY + hiddenPathHeight / 2 - 2.5
                }).show();
                se.data("host", targetElement).attr({x: hiddenPathEndX - 2.5, y: hiddenPathEndY - 2.5}).show();
            }
            if (nodeType != "End") {

                linkTool.data("from", targetElement).attr({x: hiddenPathEndX + 10, y: hiddenPathStartY}).show();
                nextTaskTool.data("from", targetElement).attr({
                    x: hiddenPathEndX + 10,
                    y: hiddenPathStartY + 16
                }).show();
                nextSplitTool.data("from", targetElement).attr({
                    x: hiddenPathEndX + 10,
                    y: hiddenPathStartY + 32
                }).show();
                nextEndTool.data("from", targetElement).attr({
                    x: hiddenPathEndX + 10,
                    y: hiddenPathStartY + 48
                }).show();

                deleteTool.attr({
                    x: hiddenPathEndX + 10 + 16,
                    y: hiddenPathStartY - 5
                }).data("host", targetElement).show();

                if (nodeType != "Start" && nodeType != "Join") {
                    exchange.data("from", targetElement).attr({
                        x: hiddenPathEndX + 10 + 16,
                        y: hiddenPathStartY + 13
                    }).show();
                }
            } else {
                deleteTool.attr({
                    x: hiddenPathEndX + 10,
                    y: hiddenPathStartY - 5
                }).data("host", targetElement).show();
            }
        } else if (type == "path") {
            // 连线
            let startElement = targetElement.data("start");
            startElement.show();
            let nextElement = startElement.data("right");
            while (nextElement) {
                nextElement.show();
                let leftRect = nextElement.data("leftRect");
                leftRect.show();
                nextElement = nextElement.data("right");
            }
            let {x, y} = targetElement.getPointAtLength(1);
            this.deleteTool.attr({x, y}).data("host", targetElement).show();
        }
        targetElement.attr("cursor", "move");
    };

    /**
     * 更新当前激活编辑的文本
     * @param textValue
     */
    updateActiveText(textValue, visible) {
        let textElement = this.textElement;
        if (textElement) {
            textElement.attr("text", textValue);
            if (visible) {
                textElement.show();
            }
        }
    };

    updateElements(targetElement) {
        let me = this;
        let text = targetElement.data("text");
        let {x, y, width, height} = targetElement.attrs;
        if (text) {
            text.attr({
                x: x + width / 2,
                y: y + height / 2
            });
            text.setWidth(width * 0.8);
        }

        let icon = targetElement.data("icon");
        if (icon) {
            icon.attr({
                x: x + 5,
                y: y + 5
            });
        }

        if (targetElement.data("in")) {
            let inLines = targetElement.data("in");
            for (let i in inLines) {
                me.updateLine(inLines[i]);
            }
        }
        if (targetElement.data("out")) {
            let outLines = targetElement.data("out");
            for (let j in outLines) {
                me.updateLine(outLines[j]);
            }
        }
    };

    updateLine(hostElement) {

        let startElement = hostElement.data("start");
        let endElement = hostElement.data("end");

        if (startElement.data("fromNode")) {
            let fromElement = startElement.data("fromNode");
            let fromElementRight = startElement.data("right");

            let isToNode = endElement == fromElementRight && endElement.data("toNode") != null;
            let linePathData = this.getPathData(fromElement, isToNode ? endElement.data("toNode") : fromElementRight);
            let pathStartPoint = linePathData.start;
            startElement.attr({
                x: pathStartPoint.x - 2.5,
                y: pathStartPoint.y - 2.5
            });

            // if (hostElement.data("container")) {
            //     this.relativePosition(startElement, hostElement.data("container"));
            // }

            // 更新rightrect的位置
            let rightControlRect = startElement.data("rightRect");
            rightControlRect.attr({
                x: (startElement.attr("x") + fromElementRight.attr("x")) / 2,
                y: (startElement.attr("y") + fromElementRight.attr("y")) / 2
            });
        }

        if (endElement.data("toNode")) {
            let toElement = endElement.data("toNode");
            let toElementLeft = endElement.data("left");

            let isFromNode = startElement == toElementLeft && startElement.data("fromNode") != null;
            let linePathData = this.getPathData(isFromNode ? startElement.data("fromNode") : toElementLeft, toElement);
            let pathEndPoint = linePathData.end;
            endElement.attr({
                x: pathEndPoint.x - 2.5,
                y: pathEndPoint.y - 2.5
            });

            // if (hostElement.data("container")) {
            //     this.relativePosition(endElement, hostElement.data("container"));
            // }

            // 更新rightrect的位置
            let leftControlRect = endElement.data("leftRect");
            leftControlRect.attr({
                x: (toElementLeft.attr("x") + endElement.attr("x")) / 2,
                y: (toElementLeft.attr("y") + endElement.attr("y")) / 2
            });
        }

        this.updatePath(hostElement);
    };

    /** 拖拽过程中实时更新path */
    updatePath(pathElement) {
        let pathStyle = pathElement.data("pathStyle");
        switch (pathStyle) {
            case "broken": {
                // 连线的开始元素
                let startElement = pathElement.data("start");
                let startRightRect = startElement.data("rightRect");
                // path文本位置更新
                pathElement.data("text").attr({
                    x: startRightRect.attr("x") - 10,
                    y: startRightRect.attr("y") - 10
                });
                let pathData = "M" + (startElement.attr("x") + 2.5) + "," + (startElement.attr("y") + 2.5);
                // let arrowPathEnd = pathElement.data("end");
                // let arrowPathStart = startElement;
                let nextElement = startElement.data("right");
                while (nextElement) {
                    // let temp = nextElement;
                    // if (hostElement.data("container")) {
                    //     let containerElement = hostElement.data("container");
                    //     let relativePosition = nextElement.data("relativePosition");
                    //     // 更新坐标
                    //     nextElement.attr({
                    //         x: containerElement.attr("x") + relativePosition.x,
                    //         y: containerElement.attr("y") + relativePosition.y
                    //     });
                    //     // 更新中点的坐标
                    //     let leftElement = nextElement.data("left");
                    //     let centerRect = nextElement.data("leftRect");
                    //     if (centerRect) {
                    //         centerRect.attr({
                    //             x: leftElement.attr("x") / 2 + nextElement.attr("x") / 2,
                    //             y: leftElement.attr("y") / 2 + nextElement.attr("y") / 2
                    //         });
                    //     }
                    // }
                    pathData += "L" + (nextElement.attr("x") + 2.5) + "," + (nextElement.attr("y") + 2.5);
                    nextElement = nextElement.data("right");
                    // if (nextElement != null) {
                    //     arrowPathStart = temp;
                    // }
                }
                //console.log(pathData);
                pathElement.attr("d", pathData);

                // // 绘制箭头
                // let arrowPath = pathElement.data("arrow");
                // let arrowPathData = this.getArrowPathData(arrowPathStart.attr("x") + 2.5, arrowPathStart.attr("y") + 2.5, arrowPathEnd.attr("x") + 2.5, arrowPathEnd.attr("y") + 2.5);
                // arrowPath.attr("d", arrowPathData);
                break;
            }
            case "straight": {
                // 重置
                this.resetPathData(pathElement, pathElement.data("from"), pathElement.data("to"), pathStyle);
                break;
            }
            default: {

            }
        }
    };

    updatePathByControlRect(controlElement) {

        let hostElement = controlElement.data("host");

        // 控制点标志
        let cpIndex = controlElement.data("cpIndex");
        let leftElement = controlElement.data("left");
        let rightElement = controlElement.data("right");

        let disableRestore = controlElement.data("disableRestore");
        let restore = controlElement.data("restore");
        if (restore) {
            controlElement.attr({
                x: (leftElement.attr("x") + rightElement.attr("x")) / 2,
                y: (leftElement.attr("y") + rightElement.attr("y")) / 2
            });
            // if (hostElement.data("container")) {
            //     this.relativePosition(controlElement, hostElement.data("container"));
            // }
            return;
        }

        if (leftElement && leftElement.data("fromNode")) {
            let fromElement = leftElement.data("fromNode");
            let linePathData = this.getPathData(fromElement, controlElement);
            let pathStartPoint = linePathData.start;
            leftElement.attr({
                x: pathStartPoint.x - 2.5,
                y: pathStartPoint.y - 2.5
            });
            //		if(hostElement.data("container")) {
            //			relativePosition(leftElement,hostElement.data("container"));
            //		}
        }

        if (rightElement && rightElement.data("toNode")) {
            let toElement = rightElement.data("toNode");
            let linePathData = this.getPathData(controlElement, toElement);
            let pathEndPoint = linePathData.end;
            rightElement.attr({
                x: pathEndPoint.x - 2.5,
                y: pathEndPoint.y - 2.5
            });
            // if (hostElement.data("container")) {
            //     this.relativePosition(rightElement, hostElement.data("container"));
            // }
        }

        if (cpIndex == -1) {

            // 更新左边的center控制矩形坐标
            let leftDragRect = controlElement.data("leftRect");

            leftDragRect.attr("x", (leftElement.attr("x") + controlElement.attr("x")) / 2);
            leftDragRect.attr("y", (leftElement.attr("y") + controlElement.attr("y")) / 2);

            // 更新右边的center控制矩形坐标
            let rightDragRect = controlElement.data("rightRect");

            rightDragRect.attr("x", (rightElement.attr("x") + controlElement.attr("x")) / 2);
            rightDragRect.attr("y", (rightElement.attr("y") + controlElement.attr("y")) / 2);

            //		if(hostElement.data("container")) {
            //			relativePosition(leftDragRect,hostElement.data("container"));
            //			relativePosition(rightDragRect,hostElement.data("container"));
            //		}

            if (!disableRestore) {
                // 当拖动的矩形元素接近left和right所在的直线时，自动还原到当前直线上
                // 还原处理的操作：
                // a 删除leftDragRect和rightDragRect
                // b controlElement.data("cpIndex",0);
                // c 更新left和right2个元素的 left，leftRect，right，rightRect

                // 如何判断？根据直线的斜率?点到直线的距离判断？（更科学）
                let x0 = controlElement.attr("x"), y0 = controlElement.attr("y"),
                    x1 = leftElement.attr("x"), y1 = leftElement.attr("y"),
                    x2 = rightElement.attr("x"), y2 = rightElement.attr("y");
                let h = distanceToLine(x0, y0, x1, y1, x2, y2);
                let isOutBoundLine = (x0 - x1) * (x0 - x2) > 0 && (y0 - y1) * (y0 - y2) > 0;
                if (h <= 3 && !isOutBoundLine) {
                    leftDragRect.remove();
                    rightDragRect.remove();
                    controlElement.data("restore", true);
                    // controlElement.data("cpIndex",0);

                    leftElement.data("right", rightElement);
                    leftElement.data("rightRect", controlElement);
                    rightElement.data("left", leftElement);
                    rightElement.data("leftRect", controlElement);
                    controlElement.attr({
                        x: (leftElement.attr("x") + rightElement.attr("x")) / 2,
                        y: (leftElement.attr("y") + rightElement.attr("y")) / 2
                    });
                    //				if(hostElement.data("container")) {
                    //					relativePosition(controlElement,hostElement.data("container"));
                    //				}
                }
            }
        } else {
            // 设置标志
            controlElement.data("disableRestore", true);

            // 创建一个控制点和2个伪矩形
            let leftCenterDragRect = this.createControlDragRect((leftElement.attr("x") + controlElement.attr("x") + 5) / 2, (leftElement.attr("y") + controlElement.attr("y") + 5) / 2, hostElement);
            leftCenterDragRect.data("cpIndex", 0);
            leftCenterDragRect.data("left", leftElement);
            leftCenterDragRect.data("right", controlElement);

            let rightCenterDragRect = this.createControlDragRect((rightElement.attr("x") + controlElement.attr("x") + 5) / 2, (rightElement.attr("y") + controlElement.attr("y") + 5) / 2, hostElement);
            rightCenterDragRect.data("cpIndex", 0);
            rightCenterDragRect.data("left", controlElement);
            rightCenterDragRect.data("right", rightElement);

            controlElement.data("leftRect", leftCenterDragRect);
            controlElement.data("rightRect", rightCenterDragRect);
            rightElement.data("leftRect", rightCenterDragRect);
            leftElement.data("rightRect", leftCenterDragRect);

            // 更新关系位置
            rightElement.data("left", controlElement);
            leftElement.data("right", controlElement);

            controlElement.data("cpIndex", -1);
        }

        // 更新path
        this.updatePath(hostElement);
    };

    updatePathBound(hostElement) {

        let fromNode = hostElement.data("from");
        let toNode = hostElement.data("to");

        // 连线的开始元素
        let startElement = hostElement.data("start");
        let endElement = hostElement.data("end");

        let rightElement = startElement.data("right");
        let startRightRect = startElement.data("rightRect");

        let f = fromNode;
        let t = rightElement;
        if (rightElement == endElement) {
            // 如果path的start的right就是path的end元素，重新计算边界点
            t = toNode;
        }

        let linePathData = this.getPathData(f, t);
        let pathStartPoint = linePathData.start;

        startElement.attr({
            x: pathStartPoint.x - 2.5,
            y: pathStartPoint.y - 2.5
        });

        startRightRect.attr({
            x: (startElement.attr("x") + rightElement.attr("x")) / 2,
            y: (startElement.attr("y") + rightElement.attr("y")) / 2
        });

        toNode = hostElement.data("to");
        // 连线的开始元素
        let endLeftRect = endElement.data("leftRect");

        let leftElement = endElement.data("left");
        f = leftElement;
        if (startElement == leftElement) {
            f = fromNode;
        }
        t = toNode;
        let endLinePathData = this.getPathData(f, t);
        let pathEndPoint = endLinePathData.end;
        endElement.attr({
            x: pathEndPoint.x - 2.5,
            y: pathEndPoint.y - 2.5
        });
        endLeftRect.attr({
            x: (leftElement.attr("x") + endElement.attr("x")) / 2,
            y: (leftElement.attr("y") + endElement.attr("y")) / 2
        });

    };

    /**
     * 检测元素是否在容器范围（圈选）
     *
     * @param element
     * @param container
     */
    isDropContainer(element, container) {
        if (!element || !container) return;
        let {x, y} = element.attrs;
        // 暂时根据x,y落点判断是否在容器内
        let {x: containerX, y: containerY, width: containerW, height: containerH} = container.attrs;
        if (x >= containerX && x <= (containerX + containerW)
            && y >= containerY && y <= (containerY + containerH)) {
            return true;
        }
        return false;
    };

    // isOutContainerBoundary(x, y, w, h, containerElement) {
    //     let containerX = containerElement.attr("x");
    //     let containerY = containerElement.attr("y");
    //     let containerW = containerElement.attr("width");
    //     let containerH = containerElement.attr("height");
    //     if (x > containerX + 5 && x + w < containerX + containerW - 5
    //         && y > containerY + 5 && y + h < containerY + containerH - 5) {
    //         // 在容器内
    //         return false;
    //     } else {
    //         return true;
    //     }
    // };

    // relativePosition(targetElement, containerElement) {
    //     let position = {
    //         x: targetElement.attr("x") - containerElement.attr("x"),
    //         y: targetElement.attr("y") - containerElement.attr("y")
    //     };
    //     targetElement.data("relativePosition", position);
    // };

    /**
     * 下一个id
     *
     * @returns {*}
     */
    nextId() {
        return ++this.cnt;
    };

    // /**
    //  * 字符串ID
    //  *
    //  * @param id
    //  * @returns {string}
    //  */
    // toElementId(id) {
    //     return id.toString();
    // };

    // unbindElementFromContainer(targetElement, container) {
    //     let containerObj = this.containers[container.id];
    //     delete containerObj.elements[targetElement.id];
    // };

    // /**删除一个容器 */
    // removeContainer(container) {
    //     // 删除容器里面的所有元素
    //     let containerId = container.target.id;
    //     delete container.elements;
    //     delete container.target;
    //     this.unregister(containerId);
    //     delete this.containers[containerId];
    // };

    /** 反注册元素（删除）*/
    unregister(id) {
        this.elements[id] = null;
        delete this.elements[id];
    };

    /** 注册元素*/
    register(target) {
        let id = target.id;
        if (!id) {
            return;
        }
        this.elements[id] = target;
    };

    isConnected(fromElement, toElement, reverse) {
        let temp = {};
        let outLines = fromElement.data("out");
        temp[fromElement.id] = fromElement;
        // let testCount = 0;
        while (outLines && keys(outLines).length) {
            let nextErgodicLines = {};
            for (let i in outLines) {
                let link = outLines[i];
                let to = link.data("to");
                if (to != toElement) {
                    if (!temp[to.id]) {
                        let nextOutLines = to.data("out");
                        for (let j in nextOutLines) {
                            nextErgodicLines[j] = nextOutLines[j];
                        }
                    }
                } else {
                    return true;
                }
            }
            outLines = nextErgodicLines;
            // if (testCount++ > 1000) {
            //     console.log(" maybe bug happen ! ");
            //     return false;
            // }
        }
        // 双向判断
        if (reverse) {
            return this.isConnected(toElement, fromElement, false);
        }

        return false;
    };

    /**
     * 判断连线是否为单独的连线，源节点只有一个分支
     * */
    isAloneConnect(connect) {
        let type = connect.type;
        if (type != "path") return null;
        let source = connect.data("from");
        return source && keys(source.data("out")).length == 1;
    };

    /**
     * 判断连线是否来源于网关（split）
     * */
    getSourceGatewayType(connect) {
        let type = connect.type;
        if (type != "path") return null;
        let source = connect.data("from");
        let nodeType = source.data("nodeType");
        if (nodeType != "Split") {
            return null;
        } else {
            return source.data("gateway");
        }
    };

    /**
     * 设置元素名称
     *
     * @param element
     * @param name
     */
    setElementName(element, name) {
        let textEle = null;
        if (element && (textEle = element.data("text"))) {
            textEle.attr("text", name);
        }
    };

    // 设置箭头
    setConnectArrow(connect, stroke) {
        if (connect.node) {
            if (!stroke) {
                stroke = connect.attr("stroke");
                createColorMarker(this.paper.node, stroke);
            }
            connect.node.style.markerEnd = `url(#${connectArrowPrefix}${stroke})`;
        }
    };

    /**
     * 设置连线颜色
     *
     * @param connect
     * @param color
     */
    setConnectColor(connect, color) {
        connect.attr("stroke", color);
        this.setConnectArrow(connect, color);
        this.setConnectType(connect);
    };

    /**
     * 针对单分支连线设置连线类型（默认类型/条件类型）
     *
     * @param connect
     * @param type(Always,Script,HandlerCall)
     */
    setConnectType(connect, type) {
        connect.conditionType = type;
    };

    /**
     * 将element转化为数据(for getData)
     *
     * @param element
     * @returns {{toId, component: {textAttrs, type, attrs}, name, id, fromId}|{}}
     */
    toElementData(element) {
        // 组件类型
        let componentType = element.type;
        let {connectDatas, nodeDatas} = this;
        // 节点类型
        if (componentType != "path") {
            let node = {};
            let nodeType = element.data("nodeType");
            let textEle = element.data("text");
            let attrs = {...element.attrs};
            let component = {
                type: componentType,
                attrs,
                textAttrs: textEle && textEle.attrs
            };
            // 移除无关属性，减少输出数据大小
            delete attrs.opacity;
            delete attrs.cursor;
            if (componentType == 'image') {
                delete attrs.src;
                delete component.textAttrs;
            }
            // node
            assign(node, {
                id: element.id,
                name: textEle && textEle.attr("text"),
                type: nodeType,
                ...getElementDatas(element, nodeDatas),
                component
            });
            return node;
        } else {
            let from = element.data("from");
            let to = element.data("to");
            let pathText = element.data("text");
            let connect = {
                id: element.id,
                name: pathText.attr("text"),
                fromId: from.id,
                toId: to.id,
                ...getElementDatas(element, connectDatas),
                component: {
                    type: componentType,
                    // 线段(broken)/直线（line）/水平-垂直（h2v）/垂直-水平(v2h)/贝塞尔曲线（curve）
                    attrs: element.attrs,
                    textAttrs: pathText.attrs,
                    // arrowAttrs: element.data("arrow").attrs,
                }
            }
            return connect;
        }
    };

    /**
     * 将数据转化为element(for setData)
     *
     * @param elementData
     */
    fromElementData(elementData) {
        let {id, type, gateway, component} = elementData;
        let element;
        let {connectDatas, nodeDatas, elements} = this;
        if (component.type == "path") {
            let {fromId, toId} = elementData;
            let fromElement = elements[fromId];
            let toElement = elements[toId];
            element = this.createConnectElement(elementData, fromElement,
                toElement);
            setElementDatas(element, connectDatas, elementData);
            this.setConnectType(element, elementData.conditionType);
        } else {
            switch (type) {
                case "Start": {
                    // element = this.loadImageElement(id, imgs.start, component, "Start");
                    element = this.loadHTMLElement(id, "start", component, "Start"); //.attr({color: this.themeColor});
                    setElementDatas(element, nodeDatas, elementData);
                    break;
                }
                case "End": {
                    // element = this.loadImageElement(id, imgs.end, component, "End");
                    element = this.loadHTMLElement(id, "end", component, "End"); //.attr({color: this.themeColor});
                    setElementDatas(element, nodeDatas, elementData);
                    break;
                }
                case "Split": {
                    // 网关类型gateway： xor, or, and
                    let splitType = gateway.toLowerCase();
                    element = this.loadHTMLElement(id, splitType, component, "Split", (node) => new HtmlSplitElementData(node));
                    setElementDatas(element, nodeDatas, elementData);
                    break;
                }
                case "Join": {
                    element = this.loadHTMLElement(id, "join", component, "Join"); //.attr({color: this.themeColor});
                    setElementDatas(element, nodeDatas, elementData);
                    break;
                }
                default: {
                    element = this.createNodeElement(elementData);
                    setElementDatas(element, nodeDatas, elementData);
                    element.data("nodeType", type);
                }
            }
        }

        elements[id] = element;
    };

    /**
     * 获取绘制的流程图数据
     *
     * */
    getData() {
        let {processId: id, processName: name} = this;
        let data = {id, name};
        let startNodeId = null;
        let {elements} = this;
        let nodes = (data.nodes = []), connects = (data.connects = []);
        for (let id in elements) {
            let element = elements[id];
            let elementData = this.toElementData(element);
            // 组件类型
            let componentType = element.type;
            // 节点类型
            if (componentType != "path") {
                let nodeType = element.data("nodeType");
                if (nodeType == "Start") {
                    startNodeId = id;
                }
                nodes.push(elementData);
            } else {
                connects.push(elementData);
            }
        }

        data.startNodeId = startNodeId;
        return data;
    };

    /**
     * 设置数据 回显流程图
     *
     * @param data     输入JSON数据
     */
    setData(data) {
        // 如果传入字符串作为JSON字符串解析为对象
        if (typeof data == "string") {
            try {
                data = JSON.parse(data);
            } catch (err) {
            }
        }
        let {id: processId, name: processName, nodes = [], connects = []} = data || {};
        this.reset();
        assign(this, {processId, processName});
        for (let node of nodes) {
            this.fromElementData(node);
        }

        // 最后初始化连线
        for (let connect of connects) {
            this.fromElementData(connect);
        }

        this.setElementsColor(this.themeColor);
    };

    /**
     * 校验返回错误信息，如果通过返回null
     *
     * @returns {null}
     */
    validate() {
        let {processId, processName} = this;
        // 校验流程id是否为空
        if (!processId) {
            return "流程id不能为空"
        }
        if (!processName) {
            return "流程名称不能为空"
        }
        let startNodeIds = [];
        let endNodeIds = [];
        let {elements} = this;
        let nodeElements = [];
        for (let id in elements) {
            let element = elements[id];
            // 组件类型
            let componentType = element.type;
            // 节点类型
            if (componentType != "path") {
                let nodeType = element.data("nodeType");
                let textEle = element.data("text");
                let name = textEle && textEle.attr("text");
                // 入口
                let inLines = element.data("in") || {};
                // 判断是否有出口
                let outLines = element.data("out") || {};
                if (nodeType == "Start") {
                    startNodeIds.push(id);
                    if (keys(outLines).length == 0) {
                        this.setSelectElement(element);
                        return "开始节点没有定义出口";
                    }
                } else if (nodeType == "End") {
                    endNodeIds.push(id);
                    if (keys(inLines).length == 0) {
                        this.setSelectElement(element);
                        return "结束节点没有定义入口";
                    }
                } else {
                    // 没有定义入口
                    if (keys(inLines).length == 0) {
                        this.setSelectElement(element);
                        return `节点[id=${id},name=${name}]没有定义入口`
                    }
                    // 判断是否有出口
                    let outLineKeys = [];
                    if ((outLineKeys = keys(outLines)).length == 0) {
                        this.setSelectElement(element);
                        return `节点[id=${id},name=${name}]没有定义出口`
                    }
                    nodeElements.push(element);
                }
            }
        }

        // 网关（分支）
        let splitElements = [];
        // 网关（聚合）
        let joinElements = [];

        // 检查节点闭环
        for (let nodeElement of nodeElements) {
            let id = nodeElement.id;
            let textEle = nodeElement.data("text");
            let name = textEle && textEle.attr("text");
            // 检查是否存在闭环
            if (this.checkClosedLoop(nodeElement)) {
                this.setSelectElement(nodeElement);
                return `从节点[id=${id},name=${name}]开始存在闭环`
            }
            // 节点类型
            let nodeType = nodeElement.data("nodeType");
            // 网关类型
            let gateway = nodeElement.data("gateway");
            if (nodeType == "Split" && gateway != "XOR") {
                splitElements.push(nodeElement);
            }
            if (nodeType == "Join") {
                joinElements.push(nodeElement);
            }
        }

        let startCount = startNodeIds.length;
        if (startCount == 0) {
            return "流程没有找到开始节点"
        } else if (startCount > 1) {
            return "流程开始节点有且只能有一个"
        }
        if (endNodeIds.length == 0) {
            return "流程没有找到结束节点"
        }

        // 配对网关校验
        for (let splitElement of splitElements) {
            let joinElement = this.matchJoinElement(splitElement, joinElements);
            let {id} = splitElement;
            if (!joinElement) {
                this.setSelectElement(splitElement);
                return `并行分支节点[id=${id}]没有匹配到相对应的聚合网关。`
            }
            // 从joinElements中移除joinElement
            let joinIndex = joinElements.indexOf(joinElement);
            joinElements.splice(joinIndex, 1);
        }

        return null;
    };

    // 匹配聚合节点
    matchJoinElement(splitElement, joinElements) {
        if (!joinElements || joinElements.length == 0) return null;
        let exitPaths = this.getExitPaths(splitElement);

        let matchedElements = [];
        for (let joinElement of joinElements) {
            // 检查splitElement和joinElement是否配对
            // 检查从splitElement开始的所有出口路径中是否存在一条路径不经过joinElement；
            // 如果所有的路径都经过joinElement，说明配对成功，否则配对失败；
            // 如果匹配到多个joinElement说明路径存在包含关系，取离分支元素最近的一个聚合网关作为配对网关
            let joinElementId = joinElement.id;
            let continueLoop = false;
            let maxIndex = 0;
            for (let exitPath of exitPaths) {
                let joinIndex = exitPath.indexOf(joinElementId);
                if (joinIndex == -1) {
                    continueLoop = true;
                    break;
                }
                maxIndex = max(maxIndex, joinIndex);
            }
            if (continueLoop) continue;
            matchedElements.push({
                maxIndex, joinElement
            });
        }
        if (matchedElements.length == 0) return null;
        // 返回maxIndex最小的网关
        matchedElements.sort((m1, m2) => {
            return m1.maxIndex > m2.maxIndex ? -1 : 1;
        })
        return matchedElements[0].joinElement;
    };

    /**
     * 获取节点元素的所有出口路径(递归实现)
     *
     * @param element
     * @param excludeKeys
     * @return 二维数组
     */
    getExitPaths(nodeElement, excludeKeys) {
        if (!nodeElement) return null;
        let nodeType = nodeElement.data("nodeType");
        if (nodeType == "End" || nodeType == "Termination") {
            // 出口
            return [[nodeElement.id]];
        }
        let outLines = nodeElement.data("out");
        if (!outLines || keys(outLines).length == 0) {
            // 理论上代码不可达，没有出口的节点视为出口
            return [[nodeElement.id]];
        }
        if (!excludeKeys) excludeKeys = [];
        let exitPaths = [];
        for (let lineId in outLines) {
            let toElement = outLines[lineId].data("to");
            if (!excludeKeys.includes(lineId)) {
                let outExitPaths = this.getExitPaths(toElement, [...excludeKeys, lineId]);
                for (let outExitPath of outExitPaths) {
                    outExitPath.unshift(nodeElement.id);
                    exitPaths.push(outExitPath);
                }
            }
        }
        return exitPaths;
    };

    /**
     * 检查节点是否形成了闭环,无法结束
     *
     * @return true 闭环； false 无闭环；
     */
    checkClosedLoop(element) {
        // 是否可关闭的
        let closedAble = (element, historyIds) => {
            let nodeType = element.data("nodeType");
            if (nodeType == "End") {
                return true;
            }
            let id = element.id;
            if (historyIds.includes(id)) {
                return false;
            } else {
                historyIds.push(id);
            }
            let outLines = element.data("out") || {};
            if (keys(outLines).length == 0) {
                return false;
            }
            for (let lineId in outLines) {
                let outLine = outLines[lineId];
                let toElement = outLine.data("to");
                let closed = closedAble(toElement, historyIds);
                if (closed) {
                    // 如果找到了结束返回true
                    return true;
                }
            }
            // 最后返回false
            return false;
        }
        // 不可关闭代表出现闭环
        return !closedAble(element, []);
    };

    /** 设置编辑模式 */
    setEditable(editable) {
        this.option.editable = editable;
        if (!editable) {
            this.hideEditElements(this.selectElement);
        }
        this.endInputEdit();
    };

    /**
     * 根据id获取元素
     */
    getElementById(id) {
        return this.elements[id];
    };

    /***
     * 自定义查找元素
     *
     * @param fn
     */
    getElementByUUID(uuid) {
        if (!uuid) return null;
        let elementValues = values(this.elements);
        for (let elementValue of elementValues) {
            let data = elementValue.data();
            if (data.uuid == uuid) {
                return elementValue;
            }
        }
        return null;
    };

    // get start node
    getStartNode() {
        let {startNodeId} = this.getData();
        return this.getElementById(startNodeId);
    }

    /**
     * get next nodes
     *
     * @param data
     * @returns {null|*[]}
     */
    getNextNodes(data) {
        if (!data) return null;
        let element = typeof data == "string" ? this.getElementById(data) : data;
        if (!element) return null;
        let nodes = [];
        try {
            let outLines = element.data("out");
            for (let lineId in outLines) {
                let line = outLines[lineId];
                nodes.push(this.toElementData(line.to));
            }
        } catch (err) {
        }
        return nodes;
    }

    /**
     * 重置所有元素的颜色
     */
    setElementsColor(color) {
        let {connectColors, dom, elements, groupSelection, nw, w, sw, n, s, ne, e, se, dashOuterPath} = this;
        if (!connectColors.includes(color)) {
            connectColors.push(color);
            createColorMarker(dom, color);
        }

        // 更新元素的颜色
        for (let elementId in elements) {
            let element = elements[elementId];
            let type = element.type;
            if (type == "path") {
                this.setConnectColor(element, color);
            } else if (type == "html") {
                element.attr("color", color);
            } else {
                element.attr("stroke", color);
                let icon = element.data("icon");
                if (icon) {
                    icon.attr("color", color);
                }
            }
        }

        // 更新其他元素的颜色
        nw.attr("stroke", color);
        w.attr("stroke", color);
        sw.attr("stroke", color);
        n.attr("stroke", color);
        s.attr("stroke", color);
        ne.attr("stroke", color);
        e.attr("stroke", color);
        se.attr("stroke", color);
        dashOuterPath.attr("stroke", color);
        groupSelection.attr("stroke", color);
    };

    /**
     * 修改主题颜色
     *
     * @param color
     */
    setThemeColor(color) {
        // 更新主题颜色
        this.settings.themeColor = color;
        // 重置元素颜色
        this.setElementsColor(color);
        // 菜单颜色
        this.setMenuStyle({
            color
        });
        // 工具栏颜色
        this.setToolsStyle({
            color
        });
        // 弹出菜单颜色
        this.popupMenu.attr({
            color
        });
    };

    /**
     * 提示信息
     *
     * @param message
     */
    alertMessage(message, level) {
        if (typeof this.option.alertMessage == "function") {
            this.option.alertMessage(message, level);
        }
    };

    /**
     * 设置元素的颜色
     *
     * @param elementId
     * @param color
     */
    setElementColorById(elementId, color) {
        let element = this.getElementById(elementId);
        this.setElementColor(element, color);
        return element;
    };

    /**
     * 设置元素的颜色
     *
     * @param uuid
     * @param color
     */
    setElementColorByUUID(uuid, color) {
        let element = this.getElementByUUID(uuid);
        this.setElementColor(element, color);
        return element;
    };

    /**
     * 设置元素的颜色
     *
     * @param element
     * @param color
     */
    setElementColor(element, color) {
        if (!element) return;
        let type = element.type;
        if (type == "path") {
            let pathAttr = {
                // fill: color,
                stroke: color
            }
            // 路径
            element.attr(pathAttr); //.data("arrow").attr(pathAttr);
            this.setConnectArrow(element);
            this.setConnectType(element);
        } else if (type == "html") {
            element.attr("color", color);
        } else {
            element.attr({
                stroke: color
            });
        }

        let icon = element.icon;
        if (icon) {
            icon.attr("color", color);
        }
    };

    // 清除记录的完成记录
    clearCompleteRecords() {
        this.completeRecords = [];
    };

    /**
     * 根据id完成一个元素状态
     */
    completeElementColorById(id, completeColor) {
        let element = this.setElementColorById(id, completeColor);
        if (element) {
            this.completeFrontLines(element, completeColor);
        }
    };

    /**
     * 根据uuid完成一个元素状态
     */
    completeElementColorByUUID(uuid, completeColor) {
        let element = this.setElementColorByUUID(uuid, completeColor);
        if (element) {
            this.completeFrontLines(element, completeColor);
        }
    };

    completeFrontLines(element, completeColor) {
        console.log(" completeFrontLines ", this.completeRecords);
        if (!element) return;
        if (!this.completeRecords) {
            this.completeRecords = [];
        }
        this.completeRecords.push(element.id);
        let inlines = element.data("in");
        for (let lineId in inlines) {
            let connectElement = inlines[lineId];
            if (!this.completeRecords.includes(lineId)) {
                this.completeRecords.push(lineId);
                let fromElement = connectElement.data("from");
                if (this.completeRecords.includes(fromElement.id)) {
                    // 这里判断连线类型是否为自动的，如果自动的标位完成
                    console.log("connectElement.conditionType ", connectElement.conditionType);
                    if(connectElement.conditionType == "Always") {
                        // 完成连线
                        this.setElementColor(connectElement, completeColor);
                    }
                }
            }
        }
    };

    /***
     * 获取前置节点列表(可达当前元素)
     *
     * @param target
     */
    getFrontNodes(target) {
        let elements = [];
        try {
            let element = null;
            if (typeof target == "string") {
                element = this.getElementById(target);
            } else {
                element = target;
            }
            if (!element) return elements;
            if (element.type == "path") {
                element = element.data("to");
            }
            if (!element) return elements;
            // find front nodes
            let findFrontNodes = (ele, includes) => {
                let inLines = ele.data && ele.data("in");
                if (inLines) {
                    for (let lineId in inLines || {}) {
                        if (!includes.includes(lineId)) {
                            includes.push(lineId);
                            let connect = inLines[lineId];
                            let from = connect.data("from");
                            if (from != element && !elements.includes(from)) {
                                elements.unshift(from);
                                findFrontNodes(from, includes);
                            }
                        }
                    }
                }
            }
            findFrontNodes(element, []);
            return elements.map(ele => this.toElementData(ele));
        } catch (err) {
            console.error(err);
        }
        return elements;
    };

    // /**
    //  * 完成一条路径
    //  *
    //  * @param fromElementId 开始环节的id
    //  * @param toElementId   结束环节的id
    //  * @param styles        自定义样式
    //  */
    // complete(fromElementId, toElementId, completeColor) {
    //
    //     completeColor = completeColor || this.option.settings.completeColor;
    //
    //     let fromElement = this.getElementById(fromElementId);
    //     if (!fromElement) {
    //         this.alertMessage("开始节点[id=" + fromElementId + "]不存在", 5);
    //         return;
    //     }
    //     let toElement = null, connect = null;
    //     let connects = fromElement.data("out");
    //     if (connects) {
    //         for (let connectId in connects) {
    //             let conn = connects[connectId];
    //             let ele = conn.data("to");
    //             if (ele.id == toElementId) {
    //                 connect = conn;
    //                 toElement = ele;
    //                 break;
    //             }
    //         }
    //     }
    //
    //     if (!toElement) {
    //         this.alertMessage("结束节点[id=" + toElementId + "]不存在或无效", 5);
    //         return;
    //     }
    //     if (fromElement.type == "html") {
    //         fromElement.attr("color", completeColor);
    //     } else {
    //         fromElement.attr({
    //             stroke: completeColor
    //         });
    //     }
    //     if (toElement.type == "html") {
    //         toElement.attr("color", completeColor);
    //     } else {
    //         toElement.attr({
    //             stroke: completeColor
    //         });
    //     }
    //     let pathAttr = {
    //         fill: completeColor,
    //         stroke: completeColor
    //     }
    //     // 路径
    //     connect.attr(pathAttr); //.data("arrow").attr(pathAttr);
    //     this.setConnectArrow(connect);
    // };
    //
    // /**
    //  * 按节点被完成的顺序进行批量的状态标色
    //  *
    //  * */
    // completeQueue(elementIds) {
    //     if (!Array.isArray(elementIds) || elementIds.length < 2) {
    //         this.alertMessage("参数错误", 5);
    //         return;
    //     }
    //     let elementId = elementIds[0];
    //     for (let i = 1, len = elementIds.length; i < len; ++i) {
    //         this.complete(elementId, elementIds[i]);
    //         elementId = elementIds[i];
    //     }
    // };

    // 获取connect
    getConnect(fromElementId, toElementId) {
        for (let elementId in this.elements) {
            let element = this.elements[elementId];
            if (element.type == "path") {
                if (element.data("from").id == fromElementId && element.data("to").id == toElementId) {
                    return element;
                }
            }
        }
        return null;
    };

    /** 导入JSON */
    importJSON(json, ignoreFit) {
        this.setData(json);
        if (!ignoreFit) {
            this.overview();
        }
    };

    /** 导出JSON */
    exportJSON() {
        let errorMessage = this.validate();
        if (errorMessage) {
            this.alertMessage("流程图错误：" + errorMessage, 5);
            return;
        }
        let data = this.getData();
        exportTextFile(JSON.stringify(data, null, 4), `${this.processId || 'flow'}.json`)
    };

    /** 导入文件处理 */
    onImportFile(evt) {
        let me = this;
        let files = evt.target.files;
        if (files && files.length > 0) {
            let file = files[0];
            let name = file.name;
            // console.log(name);
            if (!name.toLowerCase().endsWith(".json")) {
                this.alertMessage("只支持JSON格式文件", 5);
                return;
            }
            let fileReader = new FileReader();
            fileReader.readAsText(file, "utf-8");
            fileReader.onload = (evt) => {
                let text = evt.target.result;
                me.importJSON(text);
            }
        }
        // clear file area values
        evt.target.value = null;
    };

    /** 导入json */
    handleImport() {
        this.fileInput.click();
    };

    /** 导出图片 */
    exportImage() {
        const svg = this.flowWrapper;
        const content = new XMLSerializer().serializeToString(svg);// svg.outerHTML;
        let htmlSvgcontent =
            `<svg ${xmlns} width="2000" height="2000">
                <foreignObject x="0" y="0" ${fitStyle}>
                    ${content}
                </foreignObject>
             </svg>`;

        // console.log(content);
        const src = `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(htmlSvgcontent)))}`;
        const img = new Image();
        img.src = src;
        img.onload = () => {
            console.log("l");
            const canvas = document.createElement('canvas');
            canvas.width = img.width;
            canvas.height = img.height;
            const context = canvas.getContext('2d');
            context.drawImage(img, 0, 0);
            const imgBase64 = canvas.toDataURL('image/png');
            let arr = imgBase64.split(","),
                bstr = atob(arr[1]),
                n = bstr.length,
                u8arr = new Uint8Array(n);
            while (n--) {
                u8arr[n] = bstr.charCodeAt(n);
            }
            const blob = new Blob([u8arr], {type: "image/png"});
            exportBlob(blob, `${this.processId || 'flow'}.png`);
        }
    };

    /**
     * 清除元素
     */
    clearElements() {
        // remove elements
        for (let elementId in this.elements) {
            let element = this.elements[elementId];
            element.remove();
            this.elements[elementId] = null;
        }

        // 清除svg画板
        this.paper.clear();
    };

    /**
     * 重置
     */
    reset() {
        this.clearElements();
        this.initData();
        this.initControlElements();
        this.initPaper();
    };

    /**
     * 销毁
     */
    destroy() {
        this.clearElements();
        this.paper.remove();
        this.dom.innerHTML = "";
        this.initData();
        removeEventListener("keydown", this.handleDocumentKeyDown);
        removeEventListener("keyup", this.handleDocumentKeyUp);
    }
}

// 针对流程图的指定动作做历史管理
// 创建元素（节点/连线）, 拖拽位置变化, 删除元素， 整体平移， 属性设置等
let {props, methods} = historyActions;
assign(FlowDesign.prototype, props);
assign(FlowDesign.prototype, methods);

// 导出类
export default FlowDesign;